Salesforce, Python, SQL, & other ways to put your data where you need it

Need event music? 🎸

Live and recorded jazz, pop, and meditative music for your virtual conference / Zoom wedding / yoga class / private party with quality sound and a smooth technical experience

💬 EN

#


Can you show me an example of a MutationObserver being used to make a custom web component watch for changes to a

Here’s an example of how to use a MutationObserver to watch for changes to the “aria-disabled” attribute of a

class ButtonWrapper extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.shadowRoot.innerHTML = '<slot></slot>';
    this.button = this.querySelector('button');

    if (this.button) {
      this.observer = new MutationObserver(this.handleMutations.bind(this));
      this.observer.observe(this.button, {
        attributes: true,
        attributeFilter: ['aria-disabled']
      });

      // Initial check
      this.updateWrapperState(this.button.getAttribute('aria-disabled'));
    }
  }

  disconnectedCallback() {
    if (this.observer) {
      this.observer.disconnect();
    }
  }

  handleMutations(mutations) {
    for (const mutation of mutations) {
      if (mutation.type === 'attributes' && mutation.attributeName === 'aria-disabled') {
        this.updateWrapperState(mutation.target.getAttribute('aria-disabled'));
      }
    }
  }

  updateWrapperState(ariaDisabled) {
    if (ariaDisabled === 'true') {
      this.setAttribute('data-button-disabled', '');
    } else {
      this.removeAttribute('data-button-disabled');
    }
  }
}

customElements.define('button-wrapper', ButtonWrapper);

This custom component does the following:

  1. It creates a shadow DOM and uses a to allow the button to be placed inside the wrapper[^1_1].
  2. In the connectedCallback, it finds the first
  3. It sets up a MutationObserver to watch for changes to the “aria-disabled” attribute on the button1.
  4. When changes occur, the handleMutations method is called, which updates the wrapper’s state based on the button’s “aria-disabled” attribute21.
  5. The wrapper’s state is updated by setting or removing a data-button-disabled attribute, which can be used for styling or other purposes2.
  6. The observer is disconnected when the component is removed from the DOM to prevent memory leaks1.

To use this component, a frontend developer could write HTML like this:

<button-wrapper>
  <button aria-disabled="false">Click me</button>
</button-wrapper>

This approach allows the to be managed as a standalone library while still interacting with the


Why do I need to putz around with Shadow DOM?

Shadow DOM is a powerful web technology that provides several key benefits for web developers:

  1. Encapsulation: Shadow DOM allows you to create a separate, isolated DOM tree for your web components34. This means the internal structure, styling, and behavior of your component are hidden from the rest of the document, preventing conflicts with other parts of your application5.
  2. Scoped CSS: Styles defined within a Shadow DOM are scoped to that component only. This eliminates the risk of CSS conflicts and allows you to use simpler, more generic class names without worrying about naming collisions34.
  3. Simplified maintenance: By encapsulating your component’s HTML, CSS, and JavaScript, Shadow DOM makes it easier to maintain and update your code. Changes to one component are less likely to affect others3.
  4. Improved performance: Scoped styles and isolated DOM trees can lead to better performance, as the browser doesn’t need to apply global styles to elements within the Shadow DOM3.
  5. Better reusability: Shadow DOM enables you to create truly modular and reusable components that work consistently across different contexts and applications36.
  6. Native browser support: Many modern browsers already use Shadow DOM for built-in elements like

While Shadow DOM does require some additional learning and setup, its benefits in terms of encapsulation, style scoping, and component isolation make it a valuable tool for building robust, maintainable web applications, especially when working with web components or custom elements7.


But what if the attribute for the inner button gets changed only in the regular dom?

If the attribute for the inner button gets changed only in the regular DOM, the MutationObserver will still be able to detect these changes. The MutationObserver we set up in the previous example is observing the actual DOM element, not the shadow DOM89.

Here’s how it works:

  1. The custom element finds the
  2. The MutationObserver is set up to watch this specific button element for changes to its attributes, particularly the ‘aria-disabled’ attribute.
  3. Any changes made to the button’s attributes in the regular DOM will trigger the MutationObserver callback.

This approach ensures that changes to the button’s attributes are detected regardless of whether they occur in the shadow DOM or the light DOM. The key is that we’re observing the actual button element, which exists in the regular DOM.

Here’s a simplified version of the code that demonstrates this:

class ButtonWrapper extends HTMLElement {
  connectedCallback() {
    this.button = this.querySelector('button');

    if (this.button) {
      this.observer = new MutationObserver(this.handleMutations.bind(this));
      this.observer.observe(this.button, {
        attributes: true,
        attributeFilter: ['aria-disabled']
      });
    }
  }

  handleMutations(mutations) {
    for (const mutation of mutations) {
      if (mutation.type === 'attributes' && mutation.attributeName === 'aria-disabled') {
        console.log('aria-disabled attribute changed');
        // Handle the change here
      }
    }
  }
}

customElements.define('button-wrapper', ButtonWrapper);

This code will detect changes to the ‘aria-disabled’ attribute of the button, even if those changes are made directly in the regular DOM109. The MutationObserver is watching the actual button element, not a shadow DOM representation of it.


I’m getting a “property ‘button’ does not exist on type ‘ButtonWrapper’” error as I start typing things out.

The “property ‘button’ does not exist on type ‘ButtonWrapper’” error occurs because TypeScript doesn’t know about the ‘button’ property you’re trying to access on the ButtonWrapper class. To resolve this issue, you need to explicitly declare the ‘button’ property in your class definition. Here’s how you can fix it:

class ButtonWrapper extends HTMLElement {
  private button: HTMLButtonElement | null;

  constructor() {
    super();
    this.button = null;
  }

  connectedCallback() {
    this.button = this.querySelector('button');
    
    if (this.button) {
      // Set up your MutationObserver here
    }
  }

  // ... rest of your class implementation
}
By declaring the ‘button’ property as a private member of the class with the appropriate type (HTMLButtonElement null), you’re informing TypeScript about its existence and type. This should resolve the “property does not exist” error1112.

Remember to initialize the ‘button’ property in the constructor to avoid any potential undefined errors. In the connectedCallback method, you can then assign the actual button element to this property.

This approach ensures type safety while allowing you to work with the button element throughout your class implementation13.


Can you help me think through how to write this if I’m not sure exactly what type(s) of element within my wrapper (let’s call it just now, not ) might get `aria-disabled` flipped on and off? Maybe there will be other things besides

Certainly! If you’re dealing with a more generic that might contain various elements with `aria-disabled`, we can create a more flexible solution. Here's an approach that can handle multiple types of elements:

class Wrapper extends HTMLElement {
  private observer: MutationObserver | null = null;
  private observedElements: Set<Element> = new Set();

  connectedCallback() {
    this.setupObserver();
    this.observeChildElements();
  }

  disconnectedCallback() {
    this.observer?.disconnect();
  }

  private setupObserver() {
    this.observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === 'attributes' && mutation.attributeName === 'aria-disabled') {
          this.handleAriaDisabledChange(mutation.target as Element);
        } else if (mutation.type === 'childList') {
          this.observeChildElements();
        }
      });
    });

    this.observer.observe(this, {
      attributes: true,
      attributeFilter: ['aria-disabled'],
      childList: true,
      subtree: true
    });
  }

  private observeChildElements() {
    const elements = this.querySelectorAll('[aria-disabled]');
    elements.forEach((el) => {
      if (!this.observedElements.has(el)) {
        this.observedElements.add(el);
        this.handleAriaDisabledChange(el);
      }
    });
  }

  private handleAriaDisabledChange(element: Element) {
    const isDisabled = element.getAttribute('aria-disabled') === 'true';
    element.classList.toggle('wrapper-disabled-item', isDisabled);
    this.updateWrapperState();
  }

  private updateWrapperState() {
    const hasDisabledChild = Array.from(this.observedElements).some(
      (el) => el.getAttribute('aria-disabled') === 'true'
    );
    this.classList.toggle('wrapper-has-disabled', hasDisabledChild);
  }
}

customElements.define('custom-wrapper', Wrapper);

This implementation does the following:

  1. It observes all child elements for the aria-disabled attribute.
  2. It also watches for changes in the child list, so it can observe new elements that are added dynamically.
  3. When an aria-disabled change is detected, it updates the element’s class and the wrapper’s overall state.
  4. It uses a Set to keep track of observed elements to avoid duplicate observations.

You can use this wrapper like so:

<custom-wrapper>
  <button aria-disabled="false">Button 1</button>
  <input type="text" aria-disabled="true">
  <select aria-disabled="false">
    <option>Option 1</option>
    <option>Option 2</option>
  </select>
</custom-wrapper>

This approach allows the to handle any type of child element that might use the `aria-disabled` attribute, making it more flexible and reusable.


Oh, I forgot to mention – the thing I’m going to be doing to any child elements that have aria-disabled===”true” is addEventListener(“click”, disableEvents, true) and when it goes away, I need to removeEventListener(“click”, disableEvents, true), or something like that. disableEvents(e: Event) runs e.PreventDefault() and e.stopImmediatePropagation().

Here’s how you can modify your <Wrapper> custom element to dynamically add and remove event listeners (addEventListener and removeEventListener) for child elements that have their aria-disabled attribute toggled. This implementation ensures that disableEvents is applied when aria-disabled="true" and removed when the attribute changes back.

Implementation

class Wrapper extends HTMLElement {
  private observer: MutationObserver | null = null;

  constructor() {
    super();
  }

  connectedCallback() {
    this.setupObserver();
    this.observeChildElements();
  }

  disconnectedCallback() {
    this.observer?.disconnect();
  }

  private setupObserver() {
    this.observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.type === 'attributes' && mutation.attributeName === 'aria-disabled') {
          this.handleAriaDisabledChange(mutation.target as Element);
        } else if (mutation.type === 'childList') {
          this.observeChildElements();
        }
      });
    });

    this.observer.observe(this, {
      attributes: true,
      attributeFilter: ['aria-disabled'],
      childList: true,
      subtree: true
    });
  }

  private observeChildElements() {
    const elements = this.querySelectorAll('[aria-disabled]');
    elements.forEach((el) => this.handleAriaDisabledChange(el));
  }

  private handleAriaDisabledChange(element: Element) {
    const isDisabled = element.getAttribute('aria-disabled') === 'true';

    if (isDisabled) {
      element.addEventListener('click', this.disableEvents, true);
    } else {
      element.removeEventListener('click', this.disableEvents, true);
    }
  }

  private disableEvents(e: Event) {
    e.preventDefault();
    e.stopImmediatePropagation();
  }
}

customElements.define('custom-wrapper', Wrapper);

Key Details

  1. MutationObserver:
    • Observes changes to aria-disabled attributes on child elements.
    • Watches for child elements being added or removed dynamically.
  2. Dynamic Event Listener Management:
    • When aria-disabled="true", the disableEvents function is attached via addEventListener.
    • When aria-disabled is removed or set to "false", the event listener is detached using removeEventListener.
  3. disableEvents Function:
    • Prevents the default behavior of the event.
    • Stops the event from propagating further using stopImmediatePropagation.
  4. Child List Changes:
    • If new elements are added with aria-disabled, they are automatically observed and handled.

Example Usage

<custom-wrapper>
  <button aria-disabled="false">Button</button>
  <a href="#" aria-disabled="true">Link</a>
</custom-wrapper>

<script>
  const button = document.querySelector('button');
  const link = document.querySelector('a');

  // Dynamically toggle aria-disabled
  setTimeout(() => {
    button.setAttribute('aria-disabled', 'true');
    link.setAttribute('aria-disabled', 'false');
  }, 2000);
</script>

Explanation

  • When the aria-disabled attribute on a child element is set to "true", the wrapper adds a capturing event listener (true in addEventListener) to block all click events for that element.
  • When the attribute is removed or set to "false", the event listener is removed, allowing normal interaction with the element.
  • This approach works generically for any type of child element within <custom-wrapper>, making it flexible and reusable.

Notes

  • Be cautious about performance if there are many elements being observed. MutationObservers can handle large DOM trees, but excessive changes might introduce overhead.
  • Ensure that your logic for adding and removing listeners matches exactly (e.g., use the same listener function reference and options).

What if the aria-disabled attribute goes away altogether on an element where it was previously true? Will this code still catch that and remove the event handler?

Yes, the code will still catch the removal of the aria-disabled attribute and remove the event handler. The MutationObserver is set up to observe attribute changes, including when an attribute is removed entirely1415.

When the aria-disabled attribute is removed from an element where it was previously set to “true”, the following will occur:

  1. The MutationObserver will detect this change as an attribute mutation.
  2. The handleAriaDisabledChange method will be called for the affected element.
  3. Inside handleAriaDisabledChange, getAttribute(‘aria-disabled’) will return null or an empty string when the attribute is removed.
  4. This will cause isDisabled to be false.
  5. As a result, removeEventListener will be called, removing the previously added event handler.

This behavior ensures that the event handler is properly removed when the aria-disabled attribute is either set to “false” or removed entirely, maintaining the expected functionality of the wrapper component1617.


Will it catch a child element obtaining an aria-disabled attribute for the first time, when it initially didn’t have one?

Yes, the MutationObserver will catch a child element obtaining an aria-disabled attribute for the first time, even if it initially didn’t have one. This is because the MutationObserver is configured to observe attribute changes on all child elements within the wrapper1819.

The key aspects that enable this functionality are:

  1. The childList: true option in the observer configuration, which detects changes to the direct children of the observed node19.
  2. The subtree: true option, which extends the observation to all descendants of the node19.
  3. The attributes: true option, which specifically watches for changes to element attributes19.
  4. The attributeFilter: ['aria-disabled'] option, which can be used to focus only on changes to the ‘aria-disabled’ attribute1819.

When a child element that previously didn’t have an aria-disabled attribute receives one, it will trigger the MutationObserver callback. The callback can then check the new attribute value and apply the appropriate event listener or other logic based on whether aria-disabled is set to “true” or “false”1820.

This approach ensures that the wrapper component can dynamically respond to aria-disabled attributes being added, removed, or modified on any of its child elements, regardless of when these changes occur1819.


Okay, so calling setupObserver() and observeChildElements within the connectedCallback() is enough to keep it watching forever, until disconnectedCallback()?

Yes, calling setupObserver() and observeChildElements() within the connectedCallback() is sufficient to keep the MutationObserver watching for changes until disconnectedCallback() is called2122. This approach ensures that:

  1. The observer is set up when the custom element is connected to the DOM.
  2. It continues to watch for changes to the aria-disabled attribute and child elements throughout the element’s lifecycle.
  3. The observer will detect and handle:
    • New child elements added dynamically22
    • Existing elements gaining an aria-disabled attribute for the first time21
    • Changes to the aria-disabled attribute value21
    • Removal of the aria-disabled attribute21

The MutationObserver will continue to function until explicitly disconnected, which is typically done in the disconnectedCallback() method22. This lifecycle management ensures proper cleanup when the custom element is removed from the DOM, preventing memory leaks and unnecessary observations.


What would a unit test for the Wrapper class (and perhaps the customElements.define call to it) look like? I don’t know the first thing about how to test a Web Component.

To create a unit test for the Wrapper class and its custom element definition, we can use a testing framework like Mocha or Jasmine along with a DOM testing library like jsdom. Here’s an example of what such a test might look like:

import { JSDOM } from 'jsdom';
import { expect } from 'chai';

describe('Wrapper', () => {
  let window, document;

  before(() => {
    const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
    window = dom.window;
    document = window.document;
    global.window = window;
    global.document = document;
    global.CustomEvent = window.CustomEvent;
    global.HTMLElement = window.HTMLElement;

    // Define the Wrapper class here or import it
    class Wrapper extends HTMLElement {
      // ... (implementation as previously discussed)
    }
    customElements.define('custom-wrapper', Wrapper);
  });

  it('should create a custom-wrapper element', () => {
    const wrapper = document.createElement('custom-wrapper');
    expect(wrapper).to.be.instanceOf(Wrapper);
  });

  it('should observe aria-disabled changes on child elements', (done) => {
    const wrapper = document.createElement('custom-wrapper');
    const button = document.createElement('button');
    wrapper.appendChild(button);
    document.body.appendChild(wrapper);

    // Allow MutationObserver to set up
    setTimeout(() => {
      button.setAttribute('aria-disabled', 'true');
      
      // Allow MutationObserver to process the change
      setTimeout(() => {
        const clickEvent = new CustomEvent('click');
        button.dispatchEvent(clickEvent);
        expect(clickEvent.defaultPrevented).to.be.true;
        done();
      }, 0);
    }, 0);
  });

  it('should remove event listener when aria-disabled is set to false', (done) => {
    const wrapper = document.createElement('custom-wrapper');
    const button = document.createElement('button');
    button.setAttribute('aria-disabled', 'true');
    wrapper.appendChild(button);
    document.body.appendChild(wrapper);

    setTimeout(() => {
      button.setAttribute('aria-disabled', 'false');
      
      setTimeout(() => {
        const clickEvent = new CustomEvent('click');
        button.dispatchEvent(clickEvent);
        expect(clickEvent.defaultPrevented).to.be.false;
        done();
      }, 0);
    }, 0);
  });
});

This test suite does the following:

  1. Sets up a jsdom environment to simulate a browser context.
  2. Defines the Wrapper class and registers it as a custom element.
  3. Tests that the custom element can be created.
  4. Tests that the wrapper observes aria-disabled changes on child elements and adds event listeners accordingly.
  5. Tests that the event listener is removed when aria-disabled is set to false.

Note that this is a basic example and may need to be adjusted based on your specific implementation and testing framework. Also, testing Web Components often requires running tests in a real browser environment for full functionality, so you might consider using tools like Karma or Web Test Runner for more comprehensive testing232425.


Could I see it in Vitest, for comparison?

Certainly! Here’s how you could write similar tests using Vitest, which is becoming increasingly popular for its speed and Vue.js integration (though it works great with other frameworks too). Vitest has a similar API to Jest, so the transition is usually straightforward.

Here’s an example of how you might test the Wrapper component using Vitest:

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { Wrapper } from './Wrapper'; // Adjust the import path as needed

describe('Wrapper', () => {
  beforeEach(() => {
    // Clear any previous definitions to avoid conflicts
    if (customElements.get('custom-wrapper')) {
      customElements.delete('custom-wrapper');
    }
    customElements.define('custom-wrapper', Wrapper);
  });

  it('should create a custom-wrapper element', () => {
    const wrapper = document.createElement('custom-wrapper');
    expect(wrapper).toBeInstanceOf(Wrapper);
  });

  it('should observe aria-disabled changes on child elements', async () => {
    const wrapper = document.createElement('custom-wrapper');
    const button = document.createElement('button');
    wrapper.appendChild(button);
    document.body.appendChild(wrapper);

    // Use vi.waitFor to wait for async operations
    await vi.waitFor(() => {
      button.setAttribute('aria-disabled', 'true');
    });

    const clickEvent = new Event('click');
    button.dispatchEvent(clickEvent);
    expect(clickEvent.defaultPrevented).toBe(true);
  });

  it('should remove event listener when aria-disabled is set to false', async () => {
    const wrapper = document.createElement('custom-wrapper');
    const button = document.createElement('button');
    button.setAttribute('aria-disabled', 'true');
    wrapper.appendChild(button);
    document.body.appendChild(wrapper);

    await vi.waitFor(() => {
      button.setAttribute('aria-disabled', 'false');
    });

    const clickEvent = new Event('click');
    button.dispatchEvent(clickEvent);
    expect(clickEvent.defaultPrevented).toBe(false);
  });

  it('should handle removal of aria-disabled attribute', async () => {
    const wrapper = document.createElement('custom-wrapper');
    const button = document.createElement('button');
    button.setAttribute('aria-disabled', 'true');
    wrapper.appendChild(button);
    document.body.appendChild(wrapper);

    await vi.waitFor(() => {
      button.removeAttribute('aria-disabled');
    });

    const clickEvent = new Event('click');
    button.dispatchEvent(clickEvent);
    expect(clickEvent.defaultPrevented).toBe(false);
  });
});

Key differences and notes:

  1. Vitest uses import { describe, it, expect, beforeEach, vi } from 'vitest'; for its testing utilities.
  2. The beforeEach hook is used to ensure a fresh custom element definition for each test.
  3. Vitest provides vi.waitFor() which is useful for waiting for asynchronous operations like MutationObserver callbacks.
  4. The assertion syntax is slightly different: expect(x).toBe(y) instead of expect(x).to.be.y.
  5. Vitest typically doesn’t require setting up a separate JSDOM environment as it’s often included by default.
  6. The tests are more concise due to the use of async/await with vi.waitFor().

To run these tests with Vitest, you would typically have a configuration file (vitest.config.ts or vite.config.ts) set up, and then you can run the tests using the vitest command in your terminal.

This approach provides a clean, modern testing setup for your Web Component, leveraging Vitest’s speed and developer-friendly features.


I don’t see a delete method on customElements

You are correct. There is no built-in delete method for customElements in the Web Components API. The customElements registry, accessed through window.customElements, does not provide a direct way to undefine or remove a custom element once it has been defined2627.

This limitation is by design, as custom elements are intended to be permanent additions to the DOM API once defined. The lack of a delete method ensures consistency and prevents potential conflicts that could arise from removing or redefining custom elements that may be in use.

If you need to update or change a custom element’s behavior, the recommended approach is to create a new version of the element with a different name. This allows you to maintain backwards compatibility while introducing new functionality.

For testing or development purposes, if you absolutely need to reset the custom elements registry, you would typically need to refresh the page or create a new browsing context.


Why did I get HTMLElement is not defined when trying to run my test?

The “HTMLElement is not defined” error typically occurs when running tests in a Node.js environment, which doesn’t have access to browser-specific APIs like HTMLElement. This error can arise for several reasons:

  1. Running tests in a non-browser environment: Node.js doesn’t include DOM-related objects like HTMLElement by default2829.
  2. Incorrect test configuration: Your test setup might not be properly configured to simulate a browser environment30.
  3. Missing or incorrect imports: If you’re using TypeScript, you might be missing the necessary type definitions for DOM elements29.

To resolve this issue, you can try the following solutions:

  1. Use a testing framework that provides a simulated DOM environment, such as Jest with jsdom28.
  2. Ensure your test configuration includes the necessary setup for browser-like environments30.
  3. If using TypeScript, add the “dom” lib to your tsconfig.json file29.
  4. For Node.js environments, consider using a library like jsdom to simulate browser APIs31.
  5. Check that your test files are not inadvertently importing browser-specific code that’s not meant to run in Node.js2932.

If the issue persists, review your project’s configuration and ensure all dependencies are correctly installed and up-to-date33.


can Vitest do jsdom-like things, too?

Yes, Vitest can perform jsdom-like functionality for testing browser environments. Vitest supports multiple test environments, including jsdom, which allows you to simulate a browser-like environment for your tests34.

To use jsdom with Vitest, you can configure it in your Vitest configuration file:

// vitest.config.js
export default {
  test: {
    environment: 'jsdom'
  }
}

Alternatively, you can specify the jsdom environment for individual test files using a comment at the top of the file:

/**
 * @vitest-environment jsdom
 */

This approach provides compatibility with Jest’s @jest-environment syntax35.

Vitest also offers an experimental browser mode that allows you to run tests directly in a real browser environment, providing access to browser globals like window and document36. This can be useful for more accurate browser testing scenarios.

It’s worth noting that while jsdom simulates a browser environment, it may not perfectly replicate all browser behaviors. For more comprehensive browser testing, Vitest’s browser mode with tools like Playwright can provide enhanced reliability for component testing3738.


How do I get around “NotSupportedError: This name has already been registered in the registry.”?

To address the “NotSupportedError: This name has already been registered in the registry” error, you can implement the following strategies:

  1. Check before defining: Before registering a custom element, check if it’s already defined:
    if (!customElements.get('your-element-name')) {
      customElements.define('your-element-name', YourElementClass);
    }
    
  2. Use unique names: Ensure each custom element has a unique name to avoid conflicts3940.
  3. Versioning: If you need multiple versions of the same component, consider using versioned names:
    customElements.define('your-element-v2', YourElementClassV2);
    
  4. Inheritance for new versions: Create a new class that extends the original:
    class YourElementNew extends YourElement {}
    customElements.define('your-element-new', YourElementNew);
    
  5. Resolve dependency conflicts: Check for duplicate imports or multiple versions of the same component in your project dependencies40.
  6. Use try-catch: Wrap the define call in a try-catch block to handle potential errors gracefully:
    try {
      customElements.define('your-element', YourElement);
    } catch (error) {
      console.warn('Custom element already defined:', error);
    }
    

Remember, it’s generally better to prevent duplicate registrations rather than suppressing the error, as it may indicate underlying issues in your application structure or dependency management40.


How do I see what disablerElement actually is an instance of? The test isn’t passing

To determine the specific type of an HTML element in JavaScript, you can use several approaches:

  1. Use the instanceof operator:
    if (disablerElement instanceof HTMLInputElement) {
      console.log("It's an input element");
    } else if (disablerElement instanceof HTMLButtonElement) {
      console.log("It's a button element");
    }
    
  2. Check the tagName property:
    console.log(disablerElement.tagName);
    

This will return the tag name in uppercase, e.g., “INPUT” or “BUTTON”41.

  1. Use the constructor.name property:
    console.log(disablerElement.constructor.name);
    

This will return the name of the constructor function, e.g., “HTMLInputElement” or “HTMLButtonElement”42.

  1. Examine the nodeName property:
    console.log(disablerElement.nodeName);
    

Similar to tagName, this returns the element name in uppercase4243.

If you’re using TypeScript, you can also use type guards to narrow down the element type:

function isInputElement(el: Element): el is HTMLInputElement {
  return el instanceof HTMLInputElement;
}

if (isInputElement(disablerElement)) {
  console.log(disablerElement.value);
}

These methods will help you identify the specific type of the disablerElement and debug why your test isn’t passing44.


I want to test that 3 different cases remove the event listener. What again is the vitest syntax for clumping them together within the overall suite of tests?

To test three different cases that remove the event listener within the overall suite of tests in Vitest, you can use the describe function to group related tests together. Here’s the syntax for clumping these tests:

import { describe, it, expect } from 'vitest';

describe('Event listener removal', () => {
  it('should remove listener when aria-disabled is set to false', () => {
    // Test implementation
  });

  it('should remove listener when aria-disabled attribute is removed', () => {
    // Test implementation
  });

  it('should remove listener when element is removed from DOM', () => {
    // Test implementation
  });
});

This structure groups the three related tests under a single describe block, making the test suite more organized and easier to read. Each it function represents an individual test case within the group4546.


Can you nest describe?

Yes, you can nest describe blocks in Vitest (and most other testing frameworks). Nesting describe blocks is a great way to further organize and group related tests. Here’s an example of how you can nest describe blocks:

import { describe, it, expect } from 'vitest';

describe('Wrapper Component', () => {
  describe('Event listener management', () => {
    describe('Removing event listeners', () => {
      it('should remove listener when aria-disabled is set to false', () => {
        // Test implementation
      });

      it('should remove listener when aria-disabled attribute is removed', () => {
        // Test implementation
      });

      it('should remove listener when element is removed from DOM', () => {
        // Test implementation
      });
    });

    describe('Adding event listeners', () => {
      it('should add listener when aria-disabled is set to true', () => {
        // Test implementation
      });

      // More tests...
    });
  });

  describe('Attribute observation', () => {
    // More tests...
  });
});

This nested structure allows you to create a hierarchy of test groups, making your test suite more organized and easier to navigate. It’s particularly useful for complex components or when you have many related test cases.


Can I use afterEach() to share the same “expect” amongst the 3 removal tests, or is that an antipattern / code smell? If bad, what should I do instead?

Using afterEach() to share the same “expect” assertion among multiple tests is generally considered an antipattern or code smell in testing. Here’s why and what you should do instead:

  1. Test isolation: Each test should be independent and self-contained. Sharing expectations across tests can lead to interdependencies and make tests harder to understand and maintain47.
  2. Clear test intent: Each test should clearly express its purpose and expectations. Putting assertions in afterEach() can obscure the specific behavior being tested in each case47.
  3. Debugging difficulty: When a test fails, it’s harder to identify which specific case caused the failure if the assertion is shared in afterEach().

Instead, you should:

  1. Write separate assertions for each test case: This ensures each test is clear about what it’s verifying47.
  2. Use beforeEach() for common setup: If you have shared setup logic, use beforeEach() to prepare the test environment4748.
  3. Consider using a test factory: If you have very similar tests, create a function that generates test cases with slight variations49.

Here’s an example of how you might structure your tests:

describe('Event listener removal', () => {
  beforeEach(() => {
    // Common setup code
  });

  it('should remove listener when aria-disabled is set to false', () => {
    // Test-specific setup
    // Action
    expect(/* your assertion */).toBe(/* expected value */);
  });

  it('should remove listener when aria-disabled attribute is removed', () => {
    // Test-specific setup
    // Action
    expect(/* your assertion */).toBe(/* expected value */);
  });

  it('should remove listener when element is removed from DOM', () => {
    // Test-specific setup
    // Action
    expect(/* your assertion */).toBe(/* expected value */);
  });
});

This approach keeps each test focused, independent, and easy to understand, while still allowing for shared setup if needed4748.


Show me the test factory, please?

Certainly! A test factory is a great way to create multiple similar tests with slight variations. Here’s an example of how you could use a test factory for your event listener removal tests:

import { describe, it, expect } from 'vitest';
import { Wrapper } from './Wrapper'; // Adjust import path as needed

describe('Wrapper Component', () => {
  describe('Event listener removal', () => {
    const testCases = [
      {
        name: 'aria-disabled set to false',
        setup: (button: HTMLButtonElement) => button.setAttribute('aria-disabled', 'false'),
      },
      {
        name: 'aria-disabled attribute removed',
        setup: (button: HTMLButtonElement) => button.removeAttribute('aria-disabled'),
      },
      {
        name: 'element removed from DOM',
        setup: (button: HTMLButtonElement, wrapper: HTMLElement) => wrapper.removeChild(button),
      },
    ];

    const createTest = ({ name, setup }: { name: string, setup: (button: HTMLButtonElement, wrapper: HTMLElement) => void }) => {
      it(`should remove listener when ${name}`, async () => {
        const wrapper = document.createElement('custom-wrapper') as Wrapper;
        const button = document.createElement('button');
        button.setAttribute('aria-disabled', 'true');
        wrapper.appendChild(button);
        document.body.appendChild(wrapper);

        // Allow MutationObserver to set up
        await new Promise(resolve => setTimeout(resolve, 0));

        setup(button, wrapper);

        // Allow MutationObserver to process the change
        await new Promise(resolve => setTimeout(resolve, 0));

        const clickEvent = new Event('click');
        button.dispatchEvent(clickEvent);
        expect(clickEvent.defaultPrevented).toBe(false);
      });
    };

    testCases.forEach(createTest);
  });
});

This approach offers several benefits:

  1. DRY (Don’t Repeat Yourself): The common test logic is defined once in the createTest function.
  2. Flexibility: You can easily add new test cases by adding to the testCases array.
  3. Clarity: Each test case clearly specifies what’s being tested in the name property.
  4. Maintainability: If you need to change the common test logic, you only need to update it in one place.
  5. Scalability: This pattern can easily accommodate additional test cases or variations.

The createTest function encapsulates the common test logic, while the testCases array defines the specific scenarios. This combination allows for concise, yet comprehensive testing of your component’s behavior.


I can’t figure out how to both run my Vite project’s root index.html file (which has a script tag that points to the dist output) AND make sure that the dist is constantly rebuilding AND make sure that my browser actually bothers to reload the rebuilt dist.

To run your Vite project with continuous rebuilding and automatic browser reloading, you need to combine a few Vite commands and features. Here’s a step-by-step approach:

  1. Build with watch mode: Run vite build --watch to continuously rebuild your project when changes are detected50.
  2. Preview the built files: In a separate terminal, run vite preview to serve the built files from the dist directory50.
  3. Enable live reload: Unfortunately, Vite doesn’t currently have a built-in solution for live reloading the preview server. However, there’s an open issue discussing the implementation of a vite preview --watch command that would watch the output directory and trigger a page reload via websocket51.

As a workaround, you can use a browser extension for live reloading or consider using a custom setup with tools like Browsersync to handle the live reloading aspect.

Remember to keep your index.html file at the root of your project, as Vite uses it as the entry point for your application525354. This ensures that Vite can properly resolve and handle your script tags and other assets.

While this solution isn’t as seamless as Vite’s development server, it allows you to preview your built files with near-real-time updates. The Vite team is aware of this limitation and is considering improvements for future releases51.


How can I make sure the browser doesn’t cache the distributed JavaScript? (I seem to be able to both rebuild and watch using npm-run-all with both “vite” and “vite build –watch” in parallel, but my browser seems to be ignoring the changes to the dist.)

To prevent the browser from caching the distributed JavaScript and ensure it loads the latest version, you can use several techniques:

  1. Add a query parameter to the script tag: Append a unique query string to your script’s src attribute. This can be done dynamically using a timestamp or version number55:
    <script src="test.js?v=1"></script>
    

Or using a server-side language to generate a unique identifier:

<script src="test.js?v=<?php echo time(); ?>"></script>
  1. Use cache-busting headers: Configure your web server to send appropriate cache-control headers for JavaScript files55. This can be done in your server configuration or using meta tags in your HTML: ```html

3. Use the file modification time:
Instead of generating a new identifier every time, use the file's last modification time as the query parameter[^22_3]:
```php
<script src="test.js?v=<?php echo filemtime('test.js'); ?>"></script>

This approach only changes the version when the file is actually modified.

  1. For Vite specifically: If you’re using Vite, you can leverage its built-in cache-busting mechanism. Vite automatically adds a hash to the file names of your bundled assets in production builds56. This ensures that when you make changes and rebuild, the browser will load the new version.
  2. During development: For local development, modern browsers like Chrome have developer tools that can disable caching. You can open the Chrome Dev Tools and check the “Disable cache” option in the Network tab57.

Remember that while these techniques can help during development, for production deployments, it’s generally better to use a more controlled cache-busting strategy to balance between ensuring users get the latest version and leveraging browser caching for performance.


How would I echo a timestamp into the index.html of Vite? Does Vite have a templating language for that kind of thing?

Vite doesn’t have a built-in templating language for index.html, but you can use plugins to achieve this functionality. The vite-plugin-html plugin allows you to use EJS templating in your index.html file58. Here’s how you can echo a timestamp into your index.html:

  1. Install the plugin:
    npm install vite-plugin-html
    
  2. Configure the plugin in your vite.config.js or vite.config.ts file: ```javascript import { defineConfig } from ‘vite’ import { createHtmlPlugin } from ‘vite-plugin-html’

export default defineConfig({ plugins: [ createHtmlPlugin({ minify: true, inject: { data: { timestamp: new Date().toISOString() } } }) ] })


3. Use the injected data in your index.html:
```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>My Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
    <footer>
      Built at: <%- timestamp %>
    </footer>
  </body>
</html>

This approach allows you to inject dynamic data, including timestamps, into your index.html file during the build process58. The EJS templating syntax (<%- %>) is used to output the injected data.


I tried it, but it didn’t change the timestamp upon reload. It seemed to only do it once at server start-up.

You’re right, and I apologize for the confusion in my previous response. The timestamp is indeed only generated once when the Vite server starts up, not on each reload. To get a new timestamp on each reload, we need a different approach. Here’s a method that will update the timestamp on each page load:

  1. Instead of injecting the timestamp at build time, we’ll use a small piece of inline JavaScript to update the timestamp client-side.
  2. Modify your index.html like this: ```html <!DOCTYPE html>
My Vite App
Built at:

This approach will update the timestamp every time the page is loaded or reloaded, giving you a fresh timestamp on each view.

If you specifically need the timestamp for cache-busting your main JavaScript file, you could modify the script tag like this:

```html
<script type="module" src="/src/main.js?v=${Date.now()}"></script>

This will append a unique timestamp to the script URL on each page load, effectively preventing caching. However, be cautious with this approach in production as it can negatively impact performance by preventing any caching of your main JavaScript file.


I do need cache-busting, but is ${Date.now()} valid in a script tag?

Using ${Date.now()} directly in a script tag’s src attribute is not valid HTML syntax. JavaScript template literals (using backticks and ${}) don’t work in HTML attributes. Instead, you can use a more appropriate method for cache-busting:

  1. Use a server-side language to generate a timestamp.
  2. Use a build tool to inject the timestamp during the build process.
  3. Dynamically set the script’s src attribute using JavaScript.

For a client-side solution without additional build steps, you can use JavaScript to append a timestamp to the script’s src:

<script>
  var script = document.createElement('script');
  script.src = '/src/main.js?v=' + Date.now();
  document.body.appendChild(script);
</script>

This approach creates a new script element, sets its src attribute with a timestamp query parameter, and appends it to the document body5960. This ensures a unique URL for the script on each page load, effectively busting the cache.

Remember that aggressive cache-busting can negatively impact performance, so use this technique judiciously, especially in production environments.


But what server-side language am I supposed to use? Vite doesn’t come with templating and the plugin you suggested doesn’t regenerate a fresh timestamp every reload.

You’re correct that Vite doesn’t come with built-in server-side templating, and the previously suggested plugin doesn’t regenerate a fresh timestamp on every reload. For server-side rendering (SSR) with Vite, you typically use JavaScript (Node.js) as the server-side language6162.

To generate a fresh timestamp on every reload while using Vite for SSR, you can modify your server-side code to inject the timestamp dynamically. Here’s an approach you can take:

  1. Create a simple Express server that uses Vite in middleware mode.
  2. Intercept requests to your main HTML file.
  3. Read the HTML file, inject a current timestamp, and send the modified HTML to the client.

Here’s an example of how you could implement this:

import express from 'express'
import { createServer as createViteServer } from 'vite'
import fs from 'fs/promises'

async function createServer() {
  const app = express()
  const vite = await createViteServer({
    server: { middlewareMode: true },
    appType: 'custom'
  })

  app.use(vite.middlewares)

  app.use('*', async (req, res) => {
    const url = req.originalUrl
    let template = await fs.readFile('index.html', 'utf-8')
    template = await vite.transformIndexHtml(url, template)
    
    // Inject current timestamp
    const timestamp = new Date().toISOString()
    const html = template.replace('</body>', `<script>window.SERVER_TIMESTAMP="${timestamp}"</script></body>`)
    
    res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
  })

  app.listen(5173)
}

createServer()

This approach allows you to inject a server-generated timestamp into your HTML on each request, ensuring a fresh timestamp on every reload while still leveraging Vite’s development features6162.


I’m struggling. Manually testing my code in the browser, works great. However, I cannot for the life of me get `Wrapper.spec.ts` working.

I understand you’re having trouble getting your Wrapper.spec.ts test file to work, even though the component functions correctly in the browser. Let's address some common issues and potential solutions for testing Web Components with Vitest:

  1. Ensure proper test setup: Make sure your vite.config.ts file is correctly configured for testing: ```typescript import { defineConfig } from ‘vite’

export default defineConfig({ test: { environment: ‘jsdom’, globals: true, setupFiles: [’./src/setupTests.ts’], } })


2. Create a setupTests.ts file:
In your src directory, create a `setupTests.ts` file to register your custom element:
```typescript
import { Wrapper } from './Wrapper'

customElements.define('custom-wrapper', Wrapper)
  1. Write your test: In your Wrapper.spec.ts file, structure your test like this: ```typescript import { describe, it, expect } from ‘vitest’ import { Wrapper } from ‘./Wrapper’

describe(‘Wrapper’, () => { it(‘should handle aria-disabled attribute changes’, async () => { const wrapper = document.createElement(‘custom-wrapper’) as Wrapper const button = document.createElement(‘button’) button.setAttribute(‘aria-disabled’, ‘true’) wrapper.appendChild(button) document.body.appendChild(wrapper)

// Allow MutationObserver to set up
await new Promise(resolve => setTimeout(resolve, 0))

const clickEvent = new Event('click')
button.dispatchEvent(clickEvent)
expect(clickEvent.defaultPrevented).toBe(true)

button.setAttribute('aria-disabled', 'false')

// Allow MutationObserver to process the change
await new Promise(resolve => setTimeout(resolve, 0))

const secondClickEvent = new Event('click')
button.dispatchEvent(secondClickEvent)
expect(secondClickEvent.defaultPrevented).toBe(false)   }) }) ```
  1. Debugging tips:
  • Use console.log statements in your component and test to track the flow of execution.
  • Ensure that the MutationObserver in your Wrapper component is set up correctly.
  • Verify that the custom element is properly defined and recognized in the test environment.

If you’re still encountering issues, consider sharing more specific error messages or unexpected behaviors you’re seeing in your tests. This will help in providing more targeted assistance for your particular situation.


What’s the correct way to reset “document.body” every test?

To reset the document.body between tests in Vitest, you can use the afterEach hook. Here’s the recommended approach:

import { afterEach } from 'vitest'

afterEach(() => {
  document.body.innerHTML = ''
})

This method clears the content of the document.body after each test, ensuring a clean slate for the next test63.

For more comprehensive DOM resets, you can use the jsdom.reconfigure() method, which is available since Vitest 1.3.0:

import { afterEach } from 'vitest'

afterEach(() => {
  jsdom.reconfigure({
    url: 'http://localhost',
    referrer: '',
    contentType: 'text/html',
    includeNodeLocations: true,
    storageQuota: 10000000
  })
})

This approach allows you to reset the entire jsdom instance, providing a fresh environment for each test63.

Remember that Vitest uses separate workers for test files, so global state is automatically isolated between different test files. However, within the same file, you need to manually reset the DOM state to prevent leaks between tests64.


I can see from my console.log debugging that the e.preventDefault() and e.stopImmediatePropagation() are running, and yet clickEvent.defaultPrevented keeps turning out false.

Based on your debugging observations, it appears that the e.preventDefault() and e.stopImmediatePropagation() methods are being called, but they’re not having the expected effect on the clickEvent.defaultPrevented property. This unexpected behavior could be due to several reasons:

  1. Timing issues: The preventDefault() method might be called after the default action has already been triggered65.
  2. Event object mismatch: The event object you’re calling preventDefault() on might not be the same object you’re checking defaultPrevented on66.
  3. Synthetic events: If you’re using a framework like React, you might be dealing with synthetic events that behave differently from native DOM events67.

To troubleshoot this issue, try the following:

  1. Ensure you’re calling preventDefault() as early as possible in your event handler65.
  2. Verify that you’re checking defaultPrevented on the same event object that you called preventDefault() on66.
  3. If using a framework, check its documentation for any specific event handling quirks or synthetic event behaviors67.
  4. Consider using a different approach to achieve your desired behavior, such as checking a custom flag you set after calling preventDefault()68.

If the issue persists, you may need to provide more context about your specific implementation, including the framework you’re using (if any) and a minimal reproducible example of the problem.


Clarification: it’s only in vitest (with JSDom) that I’m having this problem. My browser is showing things getting stopped just fine.

Thank you for the clarification. The issue you’re experiencing with preventDefault() not working as expected in Vitest with jsdom, while functioning correctly in the browser, is a known limitation of jsdom’s event simulation.

Here are some key points to consider:

  1. jsdom’s event simulation is not perfect and may not fully replicate browser behavior, especially for complex event interactions69.
  2. The runScripts option in jsdom configuration can affect how event handlers are executed. Setting it to “dangerously” might be necessary for some event handlers to work properly70.
  3. Timing issues can occur when testing asynchronous behavior in jsdom. Ensuring that scripts are fully loaded before running tests is crucial71.

To address this issue, you can try the following:

  1. Update your jsdom configuration in Vitest:
    export default defineConfig({
      test: {
     environment: 'jsdom',
     globals: true,
     setupFiles: './testSetup.js',
     environmentOptions: {
       jsdom: {
         runScripts: 'dangerously',
       },
     },
      },
    })
    
  2. Ensure that your scripts are fully loaded before running tests:
    beforeEach(async () => {
      // Wait for scripts to load
      await new Promise(resolve => setTimeout(resolve, 0));
    });
    
  3. If the issue persists, consider using a custom flag to check if preventDefault() was called, rather than relying on defaultPrevented:
    let preventDefaultCalled = false;
    const mockEvent = {
      preventDefault: () => { preventDefaultCalled = true; },
      stopImmediatePropagation: () => {},
    };
    // Trigger your event handler with mockEvent
    expect(preventDefaultCalled).toBe(true);
    

Remember that while these workarounds can help in testing, they may not perfectly replicate browser behavior. It’s important to supplement your Vitest tests with actual browser testing for critical event-related functionality.


What are common reasons that jsdom reads “defaultprevented” wrong?

There are several common reasons why jsdom might incorrectly report the value of defaultPrevented for events:

  1. Implementation limitations: jsdom’s event simulation is not perfect and may not fully replicate browser behavior, especially for complex event interactions7273.
  2. Timing issues: The preventDefault() method might be called after the default action has already been triggered in the jsdom environment74.
  3. Event object mismatch: The event object created by jsdom is neither a true browser event nor a Node.js event, which can lead to inconsistencies in behavior73.
  4. Asynchronous script loading: If scripts are loaded asynchronously, the event handling might not be set up correctly when the event is triggered75.
  5. Incomplete implementation: Earlier versions of jsdom may have had incomplete support for event.defaultPrevented76.

To work around these issues, developers often resort to alternative methods such as:

  • Using custom flags to track whether preventDefault() was called74.
  • Ensuring scripts are fully loaded before running tests75.
  • Using the runScripts: "dangerously" option to enable more complete script execution7275.

It’s important to note that while jsdom is useful for testing, it may not perfectly replicate browser behavior, especially for event-related functionality7773.


What if we switch over to happy-dom? What would my Vitest code look like?

To switch from jsdom to happy-dom in your Vitest configuration, you’ll need to make a few changes to your setup. Here’s what your Vitest configuration would look like:

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    environment: 'happy-dom',
    // Other test configurations...
  }
})

In your test files, you can use happy-dom like this:

import { describe, it, expect } from 'vitest'

describe('Wrapper', () => {
  it('should handle aria-disabled attribute changes', async () => {
    const wrapper = document.createElement('custom-wrapper')
    const button = document.createElement('button')
    button.setAttribute('aria-disabled', 'true')
    wrapper.appendChild(button)
    document.body.appendChild(wrapper)

    // Allow for DOM updates
    await new Promise(resolve => setTimeout(resolve, 0))

    const clickEvent = new Event('click')
    button.dispatchEvent(clickEvent)
    expect(clickEvent.defaultPrevented).toBe(true)

    button.setAttribute('aria-disabled', 'false')

    // Allow for DOM updates
    await new Promise(resolve => setTimeout(resolve, 0))

    const secondClickEvent = new Event('click')
    button.dispatchEvent(secondClickEvent)
    expect(secondClickEvent.defaultPrevented).toBe(false)
  })
})

Note that happy-dom might have some differences in behavior compared to jsdom, so you may need to adjust your tests accordingly7879. Additionally, if you encounter any timing issues, you might need to add small delays or use happy-dom’s specific APIs for waiting for DOM updates78.


What are happy-dom’s specific APIs for waiting for DOM updates?

Happy-DOM does not appear to have specific APIs dedicated to waiting for DOM updates. Unlike some other testing libraries, Happy-DOM focuses on providing a lightweight and fast DOM implementation rather than offering built-in utilities for managing asynchronous DOM changes.

To handle DOM updates in tests using Happy-DOM, developers typically rely on standard JavaScript techniques:

  1. Using setTimeout or setInterval to allow time for DOM updates80.
  2. Utilizing Promise-based approaches to manage asynchronous operations80.
  3. Implementing custom polling mechanisms to check for specific DOM changes80.

If more sophisticated waiting mechanisms are required, it’s common to combine Happy-DOM with testing libraries like Testing Library, which provide utilities for querying and waiting for DOM elements81.

For precise control over DOM updates in Happy-DOM, you may need to implement custom solutions or use third-party libraries that offer such functionality.


Can you show me all of the code I need to write to leverage mocking?

To leverage mocking in Vitest, you’ll need to write code for your tests and potentially modify your configuration. Here’s a comprehensive example that demonstrates various mocking techniques:

  1. First, ensure your Vitest configuration is set up correctly: ```javascript // vite.config.js import { defineConfig } from ‘vitest/config’

export default defineConfig({ test: { environment: ‘jsdom’, globals: true, setupFiles: [’./src/setupTests.js’], } })


2. Create a setup file to handle global mocks:
```javascript
// src/setupTests.js
import { vi } from 'vitest'

// Mock global fetch
global.fetch = vi.fn()
  1. Write your tests using various mocking techniques: ```javascript // example.test.js import { describe, it, expect, vi, beforeEach, afterEach } from ‘vitest’ import { fetchData } from ‘./fetchData’ import axios from ‘axios’

describe(‘Mocking examples’, () => { beforeEach(() => { vi.useFakeTimers() })

afterEach(() => { vi.useRealTimers() vi.resetAllMocks() })

it(‘mocks fetch’, async () => { const mockData = { id: 1, name: ‘Test’ } global.fetch.mockResolvedValue({ json: () => Promise.resolve(mockData) })

const result = await fetchData('https://api.example.com/data')
expect(result).toEqual(mockData)
expect(global.fetch).toHaveBeenCalledWith('https://api.example.com/data')   })

it(‘mocks Date’, () => { const mockDate = new Date(2025, 2, 4, 9, 44) vi.setSystemTime(mockDate)

expect(new Date().toISOString()).toBe('2025-03-04T15:44:00.000Z')   })

it(‘mocks modules’, async () => { vi.mock(‘axios’) axios.get.mockResolvedValue({ data: ‘mocked data’ })

const result = await axios.get('https://api.example.com/data')
expect(result.data).toBe('mocked data')   })

it(‘uses spy on object method’, () => { const obj = { method: () => ‘original’ } const spy = vi.spyOn(obj, ‘method’) spy.mockReturnValue(‘mocked’)

expect(obj.method()).toBe('mocked')
expect(spy).toHaveBeenCalled()   })

it(‘mocks implementation of a function’, () => { const mockFn = vi.fn(() => ‘original’) mockFn.mockImplementation(() => ‘mocked’)

expect(mockFn()).toBe('mocked')   }) }) ```

This code demonstrates various mocking techniques including:

  • Mocking global fetch
  • Mocking Date and time
  • Mocking modules (axios in this case)
  • Using spies on object methods
  • Mocking function implementations

Remember to adjust the imports and function names to match your actual code structure828384.


I meant for the specific context of my Wrapper and Button and its preventDefault()

I apologize for the misunderstanding. For your specific context with the Wrapper, Button, and preventDefault(), here’s how you can leverage mocking in your tests:

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { Wrapper } from './Wrapper'

describe('Wrapper', () => {
  let wrapper: Wrapper
  let button: HTMLButtonElement
  let preventDefaultMock: vi.Mock
  let stopPropagationMock: vi.Mock

  beforeEach(() => {
    wrapper = document.createElement('custom-wrapper') as Wrapper
    button = document.createElement('button')
    button.setAttribute('aria-disabled', 'true')
    wrapper.appendChild(button)
    document.body.appendChild(wrapper)

    preventDefaultMock = vi.fn()
    stopPropagationMock = vi.fn()
  })

  afterEach(() => {
    document.body.innerHTML = ''
    vi.restoreAllMocks()
  })

  it('should prevent default when aria-disabled is true', () => {
    const clickEvent = new Event('click') as any
    clickEvent.preventDefault = preventDefaultMock
    clickEvent.stopPropagation = stopPropagationMock

    button.dispatchEvent(clickEvent)

    expect(preventDefaultMock).toHaveBeenCalled()
    expect(stopPropagationMock).toHaveBeenCalled()
  })

  it('should not prevent default when aria-disabled is false', () => {
    button.setAttribute('aria-disabled', 'false')

    const clickEvent = new Event('click') as any
    clickEvent.preventDefault = preventDefaultMock
    clickEvent.stopPropagation = stopPropagationMock

    button.dispatchEvent(clickEvent)

    expect(preventDefaultMock).not.toHaveBeenCalled()
    expect(stopPropagationMock).not.toHaveBeenCalled()
  })
})

This test suite:

  1. Creates a mock Wrapper and Button before each test.
  2. Uses vi.fn() to create mock functions for preventDefault and stopPropagation.
  3. Attaches these mock functions to a custom click event.
  4. Dispatches the custom event and checks if the mock functions were called as expected.

This approach allows you to directly verify if preventDefault() and stopPropagation() were called, bypassing the issues with defaultPrevented in jsdom.


What does vi.fn() do?

In Vitest, vi.fn() is a method used to create mock functions. These mock functions allow you to track their execution (e.g., how many times they were called, what arguments they were called with) and optionally replace their implementation. Here’s a breakdown of what vi.fn() does and how it can be used:

What vi.fn() Does

  1. Creates a mock function: It generates a new function that can be tracked during testing.
  2. Tracks calls: The mock function records details about its invocations, such as the number of calls and the arguments passed.
  3. Allows custom behavior: You can define custom return values or implementations for the mock function.

Example of Using vi.fn() in Your Context (Wrapper and Button)

Here’s how you can use vi.fn() to mock the preventDefault() and stopImmediatePropagation() methods for your <Wrapper> and <Button> test:

Test Code

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { Wrapper } from './Wrapper'

describe('Wrapper', () => {
  let wrapper: Wrapper
  let button: HTMLButtonElement
  let preventDefaultMock: vi.Mock
  let stopImmediatePropagationMock: vi.Mock

  beforeEach(() => {
    // Create the Wrapper and Button elements
    wrapper = document.createElement('custom-wrapper') as Wrapper
    button = document.createElement('button')
    button.setAttribute('aria-disabled', 'true')
    wrapper.appendChild(button)
    document.body.appendChild(wrapper)

    // Mock preventDefault and stopImmediatePropagation
    preventDefaultMock = vi.fn()
    stopImmediatePropagationMock = vi.fn()
  })

  afterEach(() => {
    // Clean up DOM and reset mocks
    document.body.innerHTML = ''
    vi.restoreAllMocks()
  })

  it('should call preventDefault and stopImmediatePropagation when aria-disabled is true', () => {
    // Create a mock event and attach mocked methods
    const clickEvent = new Event('click') as any
    clickEvent.preventDefault = preventDefaultMock
    clickEvent.stopImmediatePropagation = stopImmediatePropagationMock

    // Dispatch the event
    button.dispatchEvent(clickEvent)

    // Assert that the mocked methods were called
    expect(preventDefaultMock).toHaveBeenCalled()
    expect(stopImmediatePropagationMock).toHaveBeenCalled()
  })

  it('should not call preventDefault or stopImmediatePropagation when aria-disabled is false', () => {
    // Change aria-disabled to false
    button.setAttribute('aria-disabled', 'false')

    // Create a mock event and attach mocked methods
    const clickEvent = new Event('click') as any
    clickEvent.preventDefault = preventDefaultMock
    clickEvent.stopImmediatePropagation = stopImmediatePropagationMock

    // Dispatch the event
    button.dispatchEvent(clickEvent)

    // Assert that the mocked methods were NOT called
    expect(preventDefaultMock).not.toHaveBeenCalled()
    expect(stopImmediatePropagationMock).not.toHaveBeenCalled()
  })
})

Key Points in This Code:

  1. vi.fn() for Mocking:
    • preventDefaultMock and stopImmediatePropagationMock are created using vi.fn().
    • These mocks replace the original event methods for testing purposes.
  2. Tracking Calls:
    • expect(preventDefaultMock).toHaveBeenCalled() verifies that preventDefault() was invoked.
    • Similarly, you can track arguments with .toHaveBeenCalledWith().
  3. Custom Behavior (Optional): If needed, you can mock specific behavior for these methods:
preventDefaultMock.mockImplementation(() => console.log('prevented!'))
  1. Clean Up:
    • Use document.body.innerHTML = '' to reset the DOM after each test.
    • Call vi.restoreAllMocks() to reset all mocks to their original state.

Why Use vi.fn() Here?

  • It allows you to isolate the behavior of preventDefault() and stopImmediatePropagation(), ensuring they are invoked correctly without relying on jsdom’s potentially inconsistent handling of events.
  • You can directly verify whether these methods are called, bypassing issues with jsdom’s handling of the defaultPrevented property.

This approach ensures your tests are robust, clear, and independent of browser-specific quirks.


Why the empty parentheses in your call to vi.fn()?

The empty parentheses () in the call to vi.fn() are used to invoke the function. In JavaScript and TypeScript, which Vitest is based on, parentheses are used to call a function, even if it doesn’t take any arguments85.

vi.fn() is a function provided by Vitest to create a mock function. The empty parentheses indicate that we’re calling this function without passing any arguments. This creates a basic mock function that can be used to track calls and define custom behavior in tests8687.

If we omitted the parentheses and just wrote vi.fn, we would be referencing the function object itself without calling it, which is not what we want in this case86. The empty parentheses ensure that the function is actually executed, creating and returning a new mock function that can be used in our tests.


What’s an example of when there’d be something in the parentheses of vi.fn()?

vi.fn() can accept an optional implementation function as an argument. This is useful when you want to define a specific behavior for your mock function. Here’s an example:

const mockImplementation = () => 'mocked value';
const mockFn = vi.fn(mockImplementation);

const result = mockFn();
expect(result).toBe('mocked value');

In this case, the mock function will return ‘mocked value’ when called. This is particularly helpful when you need your mock to perform some specific action or return a predetermined value during testing8889.

You can also use vi.fn() to create more complex mocked behaviors:

const complexMock = vi.fn(() => ({
  method: vi.fn(),
  property: 'value'
}));

const instance = complexMock();
expect(instance.property).toBe('value');
expect(typeof instance.method).toBe('function');

This creates a mock function that returns an object with its own mocked method and a property, allowing for more sophisticated test scenarios9089.


Can you show me what it would look like with zombie.js instead of jsdom or happy-dom?

Zombie.js is another option for simulating a browser environment in Node.js, primarily used for testing web applications. Here’s an example of how you might use Zombie.js for testing instead of jsdom or happy-dom:

const Browser = require('zombie');
const assert = require('assert');

describe('My Web Application', function() {
  const browser = new Browser();

  before(function(done) {
    browser.visit('http://localhost:3000', done);
  });

  it('should have a title', function() {
    assert.equal(browser.text('title'), 'My Web Application');
  });

  it('should allow user input', function(done) {
    browser
      .fill('input[name="username"]', 'testuser')
      .fill('input[name="password"]', 'password123')
      .pressButton('Login', function() {
        assert.equal(browser.text('#message'), 'Welcome, testuser!');
        done();
      });
  });
});

Key differences when using Zombie.js:

  1. Browser simulation: Zombie.js creates a simulated browser environment, allowing you to interact with web pages as if using a real browser.
  2. API: Zombie.js provides methods like visit(), fill(), and pressButton() for interacting with web pages.
  3. Asynchronous testing: Many Zombie.js operations are asynchronous, so you’ll often use callbacks or Promises in your tests.
  4. Real HTTP requests: Unlike jsdom, Zombie.js can make actual HTTP requests to your application, which can be useful for more integrated testing scenarios.
  5. Headless testing: Zombie.js runs tests without a visible browser window, making it suitable for automated testing environments.

While Zombie.js can be powerful for certain testing scenarios, it’s worth noting that it’s less actively maintained compared to jsdom or happy-dom, and may not be as well-suited for modern JavaScript frameworks and testing practices.


Is there any way that working in the Typescript type “EventTarget” might help things work better?

Using the TypeScript type “EventTarget” can indeed help improve your code’s type safety and maintainability, but it may also require some additional type assertions or checks to access specific properties. Here’s how it can be beneficial:

  1. Type safety: EventTarget provides a base type for all objects that can receive events, ensuring you’re working with a consistent interface91.
  2. Browser compatibility: EventTarget is widely supported across modern browsers, making it a reliable choice for event handling92.
  3. Custom event handling: You can use EventTarget to create custom event emitters, implementing the Publisher-Subscriber pattern in a web-native way92.

However, when working with EventTarget, you may encounter some limitations:

  1. Limited properties: EventTarget only includes addEventListener(), removeEventListener(), and dispatchEvent() methods. Accessing other properties may require type assertions93.
  2. Type assertions needed: To access specific element properties, you often need to cast the EventTarget to a more specific type, like HTMLButtonElement9394.
  3. Null checks: You may need to implement null checks or type guards when working with event.target, as it can be null93.

To work around these limitations, you can use type assertions or create custom types that extend EventTarget with the properties you need9394. This approach allows you to maintain type safety while accessing the specific properties of your event targets.


I mean in my Wrapper and Button testing struggle

Ah, I see. In the context of your Wrapper and Button testing struggle, leveraging the EventTarget type can indeed help improve type safety and potentially resolve some issues. Here’s how you might approach it:

  1. Use EventTarget in your event creation:
    const clickEvent = new Event('click') as Event & {
      preventDefault: () => void;
      stopImmediatePropagation: () => void;
    };
    
  2. Modify your test setup: ```typescript import { describe, it, expect, vi, beforeEach, afterEach } from ‘vitest’ import { Wrapper } from ‘./Wrapper’

describe(‘Wrapper’, () => { let wrapper: Wrapper; let button: HTMLButtonElement;

beforeEach(() => { wrapper = document.createElement(‘custom-wrapper’) as Wrapper; button = document.createElement(‘button’); button.setAttribute(‘aria-disabled’, ‘true’); wrapper.appendChild(button); document.body.appendChild(wrapper); });

afterEach(() => { document.body.innerHTML = ‘’; });

it(‘should prevent default when aria-disabled is true’, () => { const clickEvent = new Event(‘click’) as Event & { preventDefault: () => void; stopImmediatePropagation: () => void; };

clickEvent.preventDefault = vi.fn();
clickEvent.stopImmediatePropagation = vi.fn();

button.dispatchEvent(clickEvent);

expect(clickEvent.preventDefault).toHaveBeenCalled();
expect(clickEvent.stopImmediatePropagation).toHaveBeenCalled();   });

// … other tests });


This approach:

1. Creates a more type-safe event object.
2. Uses type assertion to add the necessary methods to the Event object.
3. Mocks preventDefault and stopImmediatePropagation directly on the event object.

This method maintains type safety while allowing you to mock and test the specific methods you're interested in. It might help bypass some of the issues you've been experiencing with defaultPrevented in jsdom.

---

# Is there something I can put inside of my EventListener definition that would only be visible to my unit tests, not to browsers, to work as a sort of "you ran me" flag?

Yes, you can add a flag inside your EventListener that would only be visible to your unit tests and not to browsers. This approach is often used for testing purposes and is known as a "spy" or "mock" in testing terminology.

Here's how you can implement this:

1. Create a custom property on your event listener function:
```javascript
const myEventListener = (event) => {
  // Your event handling logic here
};

myEventListener.__testFlag = false;
  1. In your event handler, set the flag:
    const myEventListener = (event) => {
      myEventListener.__testFlag = true;
      // Your event handling logic here
    };
    
  2. In your unit tests, you can then check this flag:
    element.addEventListener('click', myEventListener);
    element.click();
    expect(myEventListener.__testFlag).toBe(true);
    

This approach allows you to verify that the event listener was called without interfering with the actual browser behavior. The __testFlag property will be ignored by browsers but can be accessed in your test environment95.

Remember that this method is primarily for testing purposes and should not be relied upon for production code logic. It’s a way to make your event listeners more testable without affecting their normal operation in a browser environment.


Can JSDom query which event listeners are attached to a given Element?

Based on the search results, JSDom does not appear to have a built-in method to query which event listeners are attached to a given Element. While browsers like Chrome and Safari offer the getEventListeners() function for this purpose, JSDom, being a pure JavaScript implementation of web standards, does not provide an equivalent functionality9697.

The search results do not indicate any direct method in JSDom to access or list event listeners attached to an element. This limitation is highlighted in several discussions and issues raised by developers trying to achieve this in JSDom9899100.

If you need to track event listeners in a JSDom environment for testing purposes, you might need to implement a custom solution. This could involve wrapping the addEventListener method to keep track of attached listeners, or using a testing library that provides such functionality.

It’s worth noting that while JSDom aims to simulate browser behavior, it doesn’t replicate all browser-specific developer tools or debugging features. The lack of a getEventListeners-like function in JSDom reflects this difference between browser environments and the JSDom testing environment.


Oh my gosh. I wonder if it’s not doing anything because there’s nothing to prevent. Whoops. How do I make the button object in my test script have an onclick?

To make the button object in your test script have an onclick handler, you can assign a function to its onclick property. Here’s how you can do it:

Example Code

import { describe, it, expect } from 'vitest'

describe('Button onclick test', () => {
  it('should trigger the onclick handler', () => {
    // Create a button element
    const button = document.createElement('button')

    // Add an onclick handler
    button.onclick = (event) => {
      event.preventDefault()
      console.log('Button clicked!')
      // You can also set a flag for testing
      ;(event.target as HTMLButtonElement).dataset.clicked = 'true'
    }

    // Simulate a click event
    const clickEvent = new Event('click', { bubbles: true, cancelable: true })
    button.dispatchEvent(clickEvent)

    // Assert that preventDefault was called
    expect(clickEvent.defaultPrevented).toBe(true)

    // Assert that the custom flag was set
    expect(button.dataset.clicked).toBe('true')
  })
})

Key Points:

  1. Assigning onclick:
    • The onclick property is assigned a function that will handle the click event.
    • Inside this function, you can call event.preventDefault() and perform any other logic.
  2. Simulating a Click Event:
    • Use button.dispatchEvent() to simulate a click event on the button.
    • Ensure the event is created with { cancelable: true } if you want to test preventDefault().
  3. Testing Side Effects:
    • In this example, we use a custom data-clicked attribute on the button to act as a “flag” that confirms the handler was executed. This is useful for testing purposes.
  4. Assertions:
    • Check that clickEvent.defaultPrevented is true to confirm that preventDefault() was called.
    • Verify the “flag” (data-clicked) to ensure the handler ran.

This approach ensures your button has an onclick handler in your test environment and allows you to verify its behavior effectively.


Instead of having to console.log all over the place, is there a way to get Node.js to just show me pretty much everything my script is doing?

Yes, there is a way to get Node.js to show you more detailed information about your script’s execution without having to add console.log statements everywhere. You can use the DEBUG environment variable to enable verbose logging for your Node.js application101102.

To enable debug logging for all modules in your application, you can set the DEBUG environment variable to a wildcard (*) when running your script:

DEBUG=* node your-script.js

This will enable debug logging for all modules that use the debug package103. Many Node.js modules and frameworks use this package for debugging purposes.

For more granular control, you can specify particular namespaces to debug:

DEBUG=your-app:* node your-script.js

This will show debug messages only for modules within your application’s namespace101.

If you want to see everything except certain namespaces, you can use the minus sign:

DEBUG=*,-not-this-namespace node your-script.js

Keep in mind that this approach relies on modules using the debug package. For your own code, you’ll need to add debug statements using the debug package:

const debug = require('debug')('your-app:module-name');

function someFunction() {
  debug('This is a debug message');
  // Your code here
}

This method provides a more structured and controllable way to view detailed information about your script’s execution without cluttering your code with console.log statements101102.


Please show me vi.spyOn() as pertains to testing my Wrapper component and its Button

To use vi.spyOn() for testing your Wrapper component and its Button, you can spy on the event handling methods. Here’s an example of how to implement this:

import { describe, it, expect, vi } from 'vitest';
import { Wrapper } from './Wrapper';

describe('Wrapper', () => {
  it('should prevent default and stop propagation when button is clicked and aria-disabled is true', () => {
    const wrapper = document.createElement('custom-wrapper') as Wrapper;
    const button = document.createElement('button');
    button.setAttribute('aria-disabled', 'true');
    wrapper.appendChild(button);

    const preventDefaultSpy = vi.spyOn(Event.prototype, 'preventDefault');
    const stopPropagationSpy = vi.spyOn(Event.prototype, 'stopPropagation');

    const clickEvent = new Event('click');
    button.dispatchEvent(clickEvent);

    expect(preventDefaultSpy).toHaveBeenCalled();
    expect(stopPropagationSpy).toHaveBeenCalled();

    preventDefaultSpy.mockRestore();
    stopPropagationSpy.mockRestore();
  });

  it('should not prevent default or stop propagation when button is clicked and aria-disabled is false', () => {
    const wrapper = document.createElement('custom-wrapper') as Wrapper;
    const button = document.createElement('button');
    button.setAttribute('aria-disabled', 'false');
    wrapper.appendChild(button);

    const preventDefaultSpy = vi.spyOn(Event.prototype, 'preventDefault');
    const stopPropagationSpy = vi.spyOn(Event.prototype, 'stopPropagation');

    const clickEvent = new Event('click');
    button.dispatchEvent(clickEvent);

    expect(preventDefaultSpy).not.toHaveBeenCalled();
    expect(stopPropagationSpy).not.toHaveBeenCalled();

    preventDefaultSpy.mockRestore();
    stopPropagationSpy.mockRestore();
  });
});

This approach uses vi.spyOn() to monitor the preventDefault and stopPropagation methods on the Event prototype. It allows you to verify that these methods are called (or not called) based on the aria-disabled attribute of the button104105.

Key points:

  1. We create spies on Event.prototype.preventDefault and Event.prototype.stopPropagation.
  2. We dispatch a click event on the button.
  3. We use expect().toHaveBeenCalled() or expect().not.toHaveBeenCalled() to verify the behavior.
  4. We restore the original methods using mockRestore() to avoid side effects in other tests106.

This method is particularly useful for integration testing, as it allows you to verify the interaction between the Wrapper component and its Button without modifying their actual behavior106105.


  1. https://html.spec.whatwg.org/dev/custom-elements.html  2 3 4

  2. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled  2

  3. https://dev.to/mukhilpadmanabhan/shadow-dom-building-perfectly-encapsulated-web-components-441f  2 3 4 5

  4. https://web.dev/articles/shadowdom-v1  2

  5. https://www.matuzo.at/blog/2023/pros-and-cons-of-shadow-dom/ 

  6. https://www.reddit.com/r/webdev/comments/e6b3d3/eli5_what_is_a_shadow_dom_and_should_we_be_using/ 

  7. https://www.blazemeter.com/blog/web-components-shadow-dom 

  8. https://stackoverflow.com/questions/68809212/javascript-dom-mutationobserver-to-detect-changes-on-lang-attributes-anywhere 

  9. https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver  2

  10. https://macarthur.me/posts/use-mutation-observer-to-handle-nodes-that-dont-exist-yet 

  11. https://www.totaltypescript.com/concepts/property-does-not-exist-on-type 

  12. https://www.reddit.com/r/typescript/comments/ktq6ta/how_do_you_deal_with_property_does_not_exist/ 

  13. https://stackoverflow.com/questions/43338763/typescript-property-does-not-exist-on-type-object 

  14. https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe 

  15. https://addyosmani.com/blog/mutation-observers/ 

  16. https://javascript.info/mutation-observer 

  17. https://stackoverflow.com/questions/20156453/how-to-detect-element-being-added-removed-from-dom-element 

  18. https://www.smashingmagazine.com/2019/04/mutationobserver-api-guide/  2 3 4

  19. https://javascript.info/mutation-observer  2 3 4 5 6

  20. https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe 

  21. https://blog.risingstack.com/writing-a-javascript-framework-the-benefits-of-custom-elements/  2 3 4

  22. https://plainvanillaweb.com/blog/articles/2024-09-16-life-and-times-of-a-custom-element/  2 3

  23. https://blog.pixelfreestudio.com/best-practices-for-testing-web-components/ 

  24. https://dev.to/ficusjs/unit-testing-web-components-with-ava-and-jsdom-2ofp 

  25. https://open-wc.org/blog/testing-web-components-with-web-test-runner/ 

  26. https://github.com/WICG/webcomponents/issues/754 

  27. https://developer.mozilla.org/en-US/docs/Web/API/Window/customElements 

  28. https://www.dhiwise.com/post/understanding-error-received-value-must-be-an-htmlelement  2

  29. https://stackoverflow.com/questions/74035705/out-of-nowhere-in-an-unrelated-file-when-testing-referenceerror-htmlelement-is  2 3 4

  30. https://www.youtube.com/watch?v=tmmIJlenDQ0  2

  31. https://developer.mescius.com/forums/spreadjs/error-when-using-with-node-js-referenceerror-htmlelement-is-not-defined 

  32. https://github.com/microsoft/TypeScript/issues/51146 

  33. https://github.com/vercel/next.js/issues/70502 

  34. https://vitest.dev/guide/environment 

  35. https://vitest.dev/config/ 

  36. https://vitest.dev/guide/browser/ 

  37. https://mayashavin.com/articles/component-testing-browser-vitest 

  38. https://github.com/vitest-dev/vitest/issues/3770 

  39. https://forums.creativeclouddeveloper.com/t/the-customelementregistry-already-contains-an-entry-with-the-same-name/7860 

  40. https://stackoverflow.com/questions/54211849/uncaught-domexception-failed-to-execute-define-on-customelementregistry-th  2 3

  41. https://developer.mozilla.org/en-US/docs/Web/API/Element/tagName 

  42. https://stackoverflow.com/questions/254302/how-can-i-determine-the-type-of-an-html-element-in-javascript  2

  43. https://www.edureka.co/community/87750/how-can-i-determine-the-type-of-an-html-element-in-javascript 

  44. https://www.reddit.com/r/typescript/comments/qf7hmt/best_way_to_identifytype_guard_dom_elements/ 

  45. https://betterstack.com/community/guides/testing/vitest-explained/ 

  46. https://vitest.dev/api/ 

  47. https://vitest.dev/api/  2 3 4 5

  48. https://blog.logrocket.com/advanced-guide-vitest-testing-mocking/  2

  49. https://app.studyraid.com/en/read/11292/352284/understanding-test-lifecycle-hooks 

  50. https://github.com/vitejs/vite/issues/13694  2

  51. https://github.com/vitejs/vite/issues/5196  2

  52. https://dev.to/codedir/understanding-vites-file-structure-why-indexhtml-belongs-at-the-root-5a0h 

  53. https://www.restack.io/p/vite-answer-index-html-guide 

  54. https://www.restack.io/p/vite-answer-index-html-location-guide 

  55. https://stackoverflow.com/questions/7413234/how-to-prevent-caching-of-my-javascript-file  2

  56. https://vite.dev/guide/build 

  57. https://dev.to/requestlyio/disable-caching-of-particular-js-css-file-2k82 

  58. https://github.com/vbenjs/vite-plugin-html  2

  59. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now 

  60. https://phoenixnap.com/kb/how-to-get-the-current-date-and-time-javascript 

  61. https://vite.dev/guide/ssr  2

  62. https://v2.vitejs.dev/guide/ssr  2

  63. https://stackoverflow.com/questions/74589653/how-can-i-reset-the-jsdom-instance-when-using-vitest-in-order-to-test-a-history  2

  64. https://github.com/vitest-dev/vitest/discussions/1741 

  65. https://github.com/hotwired/stimulus/issues/163  2

  66. https://dev.to/dvnrsn/why-isn-t-event-stoppropagation-working-1bnm  2

  67. https://www.reddit.com/r/react/comments/148it1j/stoppropagation_isnt_working/  2

  68. https://stackoverflow.com/questions/18746243/stopimmediatepropagation-not-working-as-expected 

  69. https://github.com/jsdom/jsdom 

  70. https://github.com/jsdom/jsdom/issues/1828 

  71. https://oliverjam.es/articles/frontend-testing-node-jsdom 

  72. https://classic.yarnpkg.com/en/package/tx-parser  2

  73. https://www.epicweb.dev/why-i-won-t-use-jsdom  2 3

  74. https://developer.mozilla.org/en-US/docs/Web/API/Event  2

  75. https://github.com/jsdom/jsdom  2 3

  76. https://github.com/jsdom/jsdom/issues/989 

  77. https://github.com/Rich-Harris/simulant 

  78. https://github.com/capricorn86/happy-dom/wiki/Setup-as-Test-Environment  2

  79. https://vitest.dev/guide/environment 

  80. https://stackoverflow.com/questions/51940518/is-there-any-way-to-wait-for-a-dom-to-be-updated-or-asynchronously-update-the-do  2 3

  81. https://www.jetbrains.com/guide/javascript/tutorials/eleventy-tsx/happy-dom/ 

  82. https://vitest.dev/guide/mocking 

  83. https://blog.logrocket.com/advanced-guide-vitest-testing-mocking/ 

  84. https://vitest.dev/api/mock 

  85. https://stackoverflow.com/questions/60186045 

  86. https://www.reddit.com/r/learnpython/comments/yhrzif/question_about_what_exactly_is_going_on_with/  2

  87. https://www.codecademy.com/forum_questions/55d7773ee39efeb3e90001a9 

  88. https://mayashavin.com/articles/two-shades-of-mocking-vitest 

  89. https://blog.logrocket.com/advanced-guide-vitest-testing-mocking/  2

  90. https://vitest.dev/api/mock 

  91. https://developer.mozilla.org/en-US/docs/Web/API/EventTarget 

  92. https://dev.to/itswillt/using-eventtarget-and-customevent-to-build-a-web-native-event-emitter-16hc  2

  93. https://freshman.tech/snippets/typescript/fix-value-not-exist-eventtarget/  2 3 4

  94. https://stackoverflow.com/questions/28900077/why-is-event-target-not-element-in-typescript  2

  95. https://www.jackfranklin.co.uk/blog/testing-event-listeners-javascript/ 

  96. https://inspirnathan.com/posts/121-query-event-listeners 

  97. https://github.com/jsdom/jsdom 

  98. https://github.com/jsdom/jsdom/issues/1434 

  99. https://stackoverflow.com/questions/6469667/can-i-access-the-function-body-of-an-event-listener-using-nodejs-jsdom 

  100. https://groups.google.com/g/nodejs/c/_S9RJk2RjqQ 

  101. https://www.papertrail.com/solution/tips/node-js-logging-how-to-get-started/  2 3

  102. https://docs.redhat.com/en/documentation/red_hat_build_of_node.js/12/html/node.js_runtime_guide/debugging_nodejs_based_application_nodejs  2

  103. https://stackoverflow.com/questions/29141478/how-to-enable-debug-for-node-modules 

  104. https://mayashavin.com/articles/two-shades-of-mocking-vitest 

  105. https://stevekinney.net/courses/testing/spies  2

  106. https://dev.to/axsh/mock-vs-spyon-in-vitest-with-typescript-a-guide-for-unit-and-integration-tests-2ge6  2