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:
parent
c33782e9d6
commit
3cc7a9f4e8
3 changed files with 74 additions and 27 deletions
|
|
@ -7,13 +7,23 @@ import { componentOverrides } from "./overrides";
|
||||||
* @file Automated component smoke testing suite.
|
* @file Automated component smoke testing suite.
|
||||||
* @description Iterates through all project components and performs a
|
* @description Iterates through all project components and performs a
|
||||||
* shallow mount on each. This acts as a first line of defense to catch
|
* 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.
|
* 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.
|
* @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(
|
const componentModules = import.meta.glob(
|
||||||
["../../components/**/*.vue", "!../../components/experimental/**/*.vue"],
|
["../../components/**/*.vue", "!../../components/experimental/**/*.vue"],
|
||||||
|
|
@ -131,25 +141,24 @@ describe("Unit Test", () => {
|
||||||
initialState: override.initialState || {},
|
initialState: override.initialState || {},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
stubs: {},
|
|
||||||
mocks: {
|
mocks: {
|
||||||
thingActionAvailable: vi.fn(() => true),
|
readThingProperty: () => ({}),
|
||||||
readThingProperty: vi.fn(() => ({})),
|
thingActionAvailable: () => true,
|
||||||
thingAvailable: vi.fn(() => ({})),
|
thingAvailable: () => ({}),
|
||||||
thingDescription: vi.fn(() => ({
|
thingDescription: () => ({
|
||||||
actions: {},
|
actions: {},
|
||||||
properties: {},
|
properties: {},
|
||||||
})),
|
}),
|
||||||
thingDescriptions: vi.fn(() => ({
|
thingDescriptions: () => ({
|
||||||
test_thing: {
|
test_thing: {
|
||||||
properties: {
|
properties: {
|
||||||
test_property_1: {},
|
test_property_1: {},
|
||||||
test_property_2: {},
|
test_property_2: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})),
|
}),
|
||||||
getOngoingAction: vi.fn(() => Promise.resolve()),
|
getOngoingAction: () => Promise.resolve(),
|
||||||
getThingEndpoint: vi.fn(() => Promise.resolve([])),
|
getThingEndpoint: () => Promise.resolve([]),
|
||||||
modalError: "",
|
modalError: "",
|
||||||
...(override.global?.mocks || {}),
|
...(override.global?.mocks || {}),
|
||||||
...(override.mocks || {}),
|
...(override.mocks || {}),
|
||||||
|
|
|
||||||
|
|
@ -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
|
* If a component has unmet props or global mocks setup.js is set to fail the test
|
||||||
**/
|
**/
|
||||||
export const componentOverrides = {
|
export const componentOverrides = {
|
||||||
|
"galleryContent.vue": {
|
||||||
|
global: {
|
||||||
|
mocks: {
|
||||||
|
readThingProperty: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"serverSpecifiedPropertyControl.vue": {
|
"serverSpecifiedPropertyControl.vue": {
|
||||||
props: {
|
props: {
|
||||||
propertyData: {},
|
propertyData: {},
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,20 @@
|
||||||
import { vi, beforeEach, afterEach } from "vitest";
|
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 = (() => {
|
const localStorageMock = (() => {
|
||||||
let store = {};
|
let store = {};
|
||||||
return {
|
return {
|
||||||
|
|
@ -20,35 +31,55 @@ const localStorageMock = (() => {
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Override Node's native localStorage
|
/**
|
||||||
|
* Override Node's native localStorage
|
||||||
|
*/
|
||||||
vi.stubGlobal("localStorage", localStorageMock);
|
vi.stubGlobal("localStorage", localStorageMock);
|
||||||
|
|
||||||
// Clean up
|
/**
|
||||||
|
* clean initial state before each test and set up console spies to catch warnings and errors.
|
||||||
|
*/
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
localStorage.clear();
|
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 }) => {
|
afterEach(({ task }) => {
|
||||||
if (!consoleWatchdog) return;
|
if (!consoleWarnWatchdog) return;
|
||||||
|
|
||||||
// Grab all warnings
|
/** Grab all warnings */
|
||||||
const warnings = consoleWatchdog.mock.calls;
|
const warnings = consoleWarnWatchdog.mock.calls;
|
||||||
|
|
||||||
// Clean up the spy so it doesn't leak
|
/** Clean up the spy so it doesn't leak */
|
||||||
consoleWatchdog.mockRestore();
|
consoleWarnWatchdog.mockRestore();
|
||||||
|
|
||||||
// Filter specifically for Vue warnings
|
/** Filter specifically for Vue warnings */
|
||||||
const vueWarnings = warnings.filter(
|
const vueWarnings = warnings.filter(
|
||||||
(args) => typeof args[0] === "string" && args[0].includes("[Vue warn]"),
|
(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) {
|
if (vueWarnings.length > 0) {
|
||||||
const warningMessages = vueWarnings.map((args) => args.join(" ")).join("\n\n");
|
const warningMessages = vueWarnings.map((args) => args.join(" ")).join("\n\n");
|
||||||
|
|
||||||
// Throw a plain string instead of using expect.fail() or new Error()!
|
/** 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.
|
* 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}`;
|
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}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue