ui_migration fix(vitest) Add Error watchdog that fails test if vue raises functioning errors, add specific global to gallery that raised silent error and passed test, add JDDocs for documentation

This commit is contained in:
Antonio Anaya 2026-06-17 03:38:10 -06:00 committed by Antonio Anaya
parent c33782e9d6
commit 3cc7a9f4e8
3 changed files with 74 additions and 27 deletions

View file

@ -7,13 +7,23 @@ import { componentOverrides } from "./overrides";
* @file Automated component smoke testing suite.
* @description Iterates through all project components and performs a
* shallow mount on each. This acts as a first line of defense to catch
* fatal DOM runtime errors, missing dependencies, or script crashes
* fatal DOM runtime errors, missing dependencies, memory leaks, lifecycle or script crashes
* before they reach production.
* @note CI Execution:
* When running this script in a CI/CD pipeline, it must be executed with strict memory
* safety and process isolation to handle the heavy footprint of mounting the whole set of Vue components ().
* The `test:unit` script implements the following safety nets:
* - `NODE_OPTIONS=--expose-gc` & `--logHeapUsage`: Unlocks the V8 garbage collector to aggressively clear RAM.
* - `--pool=forks` & `--isolate=true`: Boots a dedicated Node.js child process per file to prevent cross-DOM pollution.
* - `--detectAsyncLeaks`: Actively catches hanging `setIntervals` or unresolved API promises.
* - `--no-cache`: Bypasses Vite caching to guarantee a pristine execution environment.
* - `--reporter=verbose`: Forces cloud logs to print exact component names for easier debugging.
* @requires overrides.js for component-specific prop and mock configurations.
*/
/** * Vite eagerly grabs all .vue components, explicitly excluding the experimental folder.
* * @type {Record<string, { default: any }>} A dictionary mapping file paths to their Vue component modules.
/**
* Vite eagerly grabs all .vue components, explicitly excluding the experimental folder.
* @type {Record<string, { default: any }>} A dictionary mapping file paths to their Vue component modules.
*/
const componentModules = import.meta.glob(
["../../components/**/*.vue", "!../../components/experimental/**/*.vue"],
@ -131,25 +141,24 @@ describe("Unit Test", () => {
initialState: override.initialState || {},
}),
],
stubs: {},
mocks: {
thingActionAvailable: vi.fn(() => true),
readThingProperty: vi.fn(() => ({})),
thingAvailable: vi.fn(() => ({})),
thingDescription: vi.fn(() => ({
readThingProperty: () => ({}),
thingActionAvailable: () => true,
thingAvailable: () => ({}),
thingDescription: () => ({
actions: {},
properties: {},
})),
thingDescriptions: vi.fn(() => ({
}),
thingDescriptions: () => ({
test_thing: {
properties: {
test_property_1: {},
test_property_2: {},
},
},
})),
getOngoingAction: vi.fn(() => Promise.resolve()),
getThingEndpoint: vi.fn(() => Promise.resolve([])),
}),
getOngoingAction: () => Promise.resolve(),
getThingEndpoint: () => Promise.resolve([]),
modalError: "",
...(override.global?.mocks || {}),
...(override.mocks || {}),

View file

@ -7,6 +7,13 @@ import { markRaw } from "vue";
* If a component has unmet props or global mocks setup.js is set to fail the test
**/
export const componentOverrides = {
"galleryContent.vue": {
global: {
mocks: {
readThingProperty: () => [],
},
},
},
"serverSpecifiedPropertyControl.vue": {
props: {
propertyData: {},

View file

@ -1,9 +1,20 @@
import { vi, beforeEach, afterEach } from "vitest";
let consoleWatchdog;
/**
* @file: Global setup for unit tests.
* @description This file sets up the testing environment for all unit tests, including:
* - Mocking localStorage to prevent side effects and ensure test isolation.
* - Spying on console.warn and console.error to catch any Vue warnings or uncaught exceptions during component mounting.
* - Cleaning up the DOM and memory after each test to prevent leaks and ensure a fresh state for subsequent tests.
* - Enforcing strict failure on any Vue warnings or console errors to maintain high code quality and catch issues early.
*/
// Create an isolated, in-memory mock for localStorage
let consoleWarnWatchdog;
let consoleErrorWatchdog;
/**
* Mock implementation of localStorage for testing purposes.
*/
const localStorageMock = (() => {
let store = {};
return {
@ -20,35 +31,55 @@ const localStorageMock = (() => {
};
})();
// Override Node's native localStorage
/**
* Override Node's native localStorage
*/
vi.stubGlobal("localStorage", localStorageMock);
// Clean up
/**
* clean initial state before each test and set up console spies to catch warnings and errors.
*/
beforeEach(() => {
localStorage.clear();
consoleWatchdog = vi.spyOn(console, "warn");
consoleWarnWatchdog = vi.spyOn(console, "warn");
consoleErrorWatchdog = vi.spyOn(console, "error");
});
/**
* After each test, check for any Vue warnings or console errors that occurred during component mounting.
* If any warnings or errors are detected, fail the test and print the relevant messages for debugging.
*/
afterEach(({ task }) => {
if (!consoleWatchdog) return;
if (!consoleWarnWatchdog) return;
// Grab all warnings
const warnings = consoleWatchdog.mock.calls;
/** Grab all warnings */
const warnings = consoleWarnWatchdog.mock.calls;
// Clean up the spy so it doesn't leak
consoleWatchdog.mockRestore();
/** Clean up the spy so it doesn't leak */
consoleWarnWatchdog.mockRestore();
// Filter specifically for Vue warnings
/** Filter specifically for Vue warnings */
const vueWarnings = warnings.filter(
(args) => typeof args[0] === "string" && args[0].includes("[Vue warn]"),
);
// If Vue has warns forcefully fail this test block --max-warnings=0
/** If Vue has warns forcefully fail this test block --max-warnings=0 */
if (vueWarnings.length > 0) {
const warningMessages = vueWarnings.map((args) => args.join(" ")).join("\n\n");
// Throw a plain string instead of using expect.fail() or new Error()!
// Without an Error object, there is no stack trace, so Vitest CANNOT show a code snippet.
/** Throw a plain string instead of using expect.fail() or new Error()!
* Without an Error object, there is no stack trace, so Vitest CANNOT show a code snippet.
*/
throw `[Vue warn] Failure in "${task.name}":\n\n${warningMessages}`;
}
/** Add watchdog for console.error to catch any uncaught exceptions during component mounting */
if (consoleErrorWatchdog) {
const errors = consoleErrorWatchdog.mock.calls;
consoleErrorWatchdog.mockRestore();
if (errors.length > 0) {
const errorMessages = errors.map((args) => args.join(" ")).join("\n\n");
throw `[Console Error] Hard crash in "${task.name}":\n\n${errorMessages}`;
}
}
});