ui_migration feature(vitest) Revisit allComponents.spec.js add jsdocstrings
This commit is contained in:
parent
8f20e1211e
commit
5ec088e07a
2 changed files with 81 additions and 37 deletions
|
|
@ -3,16 +3,33 @@ import { shallowMount, flushPromises } from "@vue/test-utils";
|
||||||
import { createTestingPinia } from "@pinia/testing";
|
import { createTestingPinia } from "@pinia/testing";
|
||||||
import { markRaw } from "vue";
|
import { markRaw } from "vue";
|
||||||
|
|
||||||
// Eagerly import ALL .vue files at the components directory
|
/**
|
||||||
// Except the ones inside experimental
|
* @file Automated component smoke testing suite.
|
||||||
const componentModules = Object.fromEntries(
|
* @description Iterates through all project components and performs a
|
||||||
Object.entries(import.meta.glob("../../components/**/*.vue", { eager: true })),
|
* shallow mount on each. This acts as a first line of defense to catch
|
||||||
|
* fatal DOM runtime errors, missing dependencies, or script crashes
|
||||||
|
* before they reach production.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** * 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"],
|
||||||
|
{ eager: true },
|
||||||
);
|
);
|
||||||
// Skip list, add here components that have their own test spec.js file
|
|
||||||
|
/**
|
||||||
|
* IMPORTANT
|
||||||
|
* Skip list, add here components that have their own test .spec.js file
|
||||||
|
* */
|
||||||
const skipList = ["loggingContent.vue"];
|
const skipList = ["loggingContent.vue"];
|
||||||
|
|
||||||
// Map filenames to the specific props or mocks required
|
/**
|
||||||
// Not all components ask for props
|
* Map filenames to the specific props or mocks required
|
||||||
|
* Not all components ask for props
|
||||||
|
* If a component has unmet props or global mocks setup.js is set to fail the test
|
||||||
|
**/
|
||||||
const componentOverrides = {
|
const componentOverrides = {
|
||||||
"serverSpecifiedPropertyControl.vue": {
|
"serverSpecifiedPropertyControl.vue": {
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -26,8 +43,8 @@ const componentOverrides = {
|
||||||
thingDescriptions: {
|
thingDescriptions: {
|
||||||
test_thing: {
|
test_thing: {
|
||||||
properties: {
|
properties: {
|
||||||
property_1: {},
|
test_property_1: {},
|
||||||
fancy_property_2: {},
|
test_property_2: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -43,8 +60,8 @@ const componentOverrides = {
|
||||||
thingDescriptions: {
|
thingDescriptions: {
|
||||||
test_thing: {
|
test_thing: {
|
||||||
properties: {
|
properties: {
|
||||||
fancy_property_1: {},
|
test_property_1: {},
|
||||||
fancy_property_2: {},
|
test_property_2: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -60,8 +77,8 @@ const componentOverrides = {
|
||||||
thingDescriptions: {
|
thingDescriptions: {
|
||||||
test_thing: {
|
test_thing: {
|
||||||
properties: {
|
properties: {
|
||||||
fancy_property_1: {},
|
test_property_1: {},
|
||||||
fancy_property_2: {},
|
test_property_2: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -202,26 +219,39 @@ const componentOverrides = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("Automated Component Smoke Tests", () => {
|
/**
|
||||||
// Declare wrapper at this scope so afterEach can clean it up
|
* Test description this is a fast unit test
|
||||||
|
* The objective of this testing is find early
|
||||||
|
* component loading issues like bad component setups
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe("Unit Test", () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment setup
|
||||||
|
* set Fake timers and Mocks
|
||||||
|
*/
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Environment setup
|
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up after every single test
|
||||||
|
* Unmounts any present component wrapper=
|
||||||
|
* Clean up virtual DOM and memory bindings
|
||||||
|
* Forces V8 engine to immediately run Garbage Collection
|
||||||
|
* clears out timers to avoid leaks
|
||||||
|
*/
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
if (wrapper) {
|
if (wrapper) {
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
wrapper = null;
|
wrapper = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up virtual DOM and memory bindings
|
|
||||||
document.body.innerHTML = "";
|
document.body.innerHTML = "";
|
||||||
|
|
||||||
// Forces V8 engine to immediately run Garbage Collection
|
|
||||||
if (global.gc) {
|
if (global.gc) {
|
||||||
global.gc();
|
global.gc();
|
||||||
}
|
}
|
||||||
|
|
@ -234,54 +264,68 @@ describe("Automated Component Smoke Tests", () => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 15));
|
await new Promise((resolve) => setTimeout(resolve, 15));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Loop through the file paths
|
/**
|
||||||
|
* Loop through the file paths
|
||||||
|
* Unwraps the component and exposes its definition using ".default"
|
||||||
|
* Includes meta information like name, props, components, methods.
|
||||||
|
* Do not confuse with export default from script component section
|
||||||
|
* Skips components at the skipList from files that are explicitly skipped
|
||||||
|
* Also skips components which name starts with the word "experimental"
|
||||||
|
* allowing to work with edge case components if needed
|
||||||
|
* is discouraged to push components that won't pass a smoke test
|
||||||
|
* */
|
||||||
for (const path in componentModules) {
|
for (const path in componentModules) {
|
||||||
const component = componentModules[path].default;
|
const component = componentModules[path].default;
|
||||||
const fileName = path.split("/").pop();
|
const fileName = path.split("/").pop();
|
||||||
const isSkipped = skipList.includes(fileName) || path.toLowerCase().includes("experimental");
|
const isSkipped = skipList.includes(fileName) || path.toLowerCase().includes("experimental");
|
||||||
|
|
||||||
// This check skips vue files that are corrupted or have no meta content
|
/**
|
||||||
// .default has meta content even without explicit default export
|
* This check skips vue files that are corrupted or have no meta content
|
||||||
// This check serves the purpose to avoid a file halting a CI pipeline
|
* return is preferred as a wrong path in file could lead to halt on CI pipeline or over verbose output
|
||||||
if (!component) {
|
* An error here means a wrong path or file access error
|
||||||
|
* This was needed during the writing of this script
|
||||||
|
* Leaving for other future edge cases usage
|
||||||
|
* This fails the overall test
|
||||||
|
* */
|
||||||
|
if (!component || undefined) {
|
||||||
console.error(component);
|
console.error(component);
|
||||||
// Generate a test specifically to report this failure to Vitest
|
|
||||||
it(`${fileName} has a default export`, () => {
|
const errorName = fileName + "Wrong Path";
|
||||||
expect.fail(
|
|
||||||
`Test Generation Failed: No content in "${fileName}". ` +
|
it(`${errorName} has no correct Path`, () => {
|
||||||
`Please fix this file or add it to the skipList.`,
|
expect.fail(`Incorrect Path parsing for "${fileName}"`);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Skip generating the REST of the test suite for this broken file
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamically assign the test runner (it.skip if in the list, otherwise standard it)
|
/**
|
||||||
|
* Dynamically assign the test runner (it.skip if in the list, otherwise standard it)
|
||||||
|
* Run the test
|
||||||
|
* Look up the specific config for this file, default to an empty object
|
||||||
|
* Override specific props
|
||||||
|
* Inject the specific state if it exists, otherwise use empty object
|
||||||
|
* Mocks values come from MIXINS
|
||||||
|
*/
|
||||||
const testRunner = isSkipped ? it.skip : it;
|
const testRunner = isSkipped ? it.skip : it;
|
||||||
|
|
||||||
// Run the test
|
|
||||||
testRunner(`Shallow mounts ${fileName} without crashing`, async () => {
|
testRunner(`Shallow mounts ${fileName} without crashing`, async () => {
|
||||||
// Look up the specific config for this file, default to an empty object
|
|
||||||
const override = componentOverrides[fileName] || {};
|
const override = componentOverrides[fileName] || {};
|
||||||
|
|
||||||
wrapper = shallowMount(component, {
|
wrapper = shallowMount(component, {
|
||||||
attachTo: document.body,
|
attachTo: document.body,
|
||||||
|
|
||||||
// Inject the specific props here!
|
|
||||||
props: override.props || {},
|
props: override.props || {},
|
||||||
|
|
||||||
global: {
|
global: {
|
||||||
plugins: [
|
plugins: [
|
||||||
createTestingPinia({
|
createTestingPinia({
|
||||||
createSpy: vi.fn,
|
createSpy: vi.fn,
|
||||||
// Inject the specific state if it exists, otherwise use empty object
|
|
||||||
initialState: override.initialState || {},
|
initialState: override.initialState || {},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
stubs: {},
|
stubs: {},
|
||||||
mocks: {
|
mocks: {
|
||||||
// These mocks come from MIXINS
|
|
||||||
thingActionAvailable: vi.fn(() => true),
|
thingActionAvailable: vi.fn(() => true),
|
||||||
readThingProperty: vi.fn(() => ({})),
|
readThingProperty: vi.fn(() => ({})),
|
||||||
thingAvailable: vi.fn(() => ({})),
|
thingAvailable: vi.fn(() => ({})),
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ afterEach(({ task }) => {
|
||||||
(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
|
// 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");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue