ui_migration feature(unitesting) modifying vitest config and spec, add test id flags to component

This commit is contained in:
Antonio Anaya 2026-05-14 22:07:38 -06:00
parent c7d16fa43f
commit 9968978a7b
3 changed files with 90 additions and 28 deletions

View file

@ -17,6 +17,7 @@
class="uk-button uk-button-default uk-width-1-1" class="uk-button uk-button-default uk-width-1-1"
type="button" type="button"
@click="updateLogs()" @click="updateLogs()"
data-test-id="update-btn"
> >
Refresh Logs Refresh Logs
</button> </button>
@ -27,6 +28,7 @@
:url="logFileURI" :url="logFileURI"
button-label="Download Log File" button-label="Download Log File"
:button-primary="false" :button-primary="false"
data-test-id="download-btn"
/> />
</div> </div>
</div> </div>

View file

@ -2,24 +2,40 @@ import { shallowMount, flushPromises } from "@vue/test-utils";
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from "vitest";
import { createTestingPinia } from "@pinia/testing"; import { createTestingPinia } from "@pinia/testing";
import axios from "axios"; import axios from "axios";
import fs from "fs";
import path from "path";
import LoggingContent from "../../components/tabContentComponents/loggingContent.vue"; import LoggingContent from "../../components/tabContentComponents/loggingContent.vue";
// Mock Axios // Mock Axios
vi.mock("axios"); vi.mock("axios", () => {
return {
default: {
get: vi.fn(),
post: vi.fn(),
put: vi.fn(),
delete: vi.fn(),
patch: vi.fn(),
},
};
});
// Mock VueUse to prevent IntersectionObserver crashes in JSDOM // Mock VueUse to prevent IntersectionObserver crashes in JSDOM
// This will be removed once vue-route is implemented
vi.mock("@vueuse/core", () => ({ vi.mock("@vueuse/core", () => ({
useIntersectionObserver: vi.fn(() => ({ useIntersectionObserver: vi.fn(() => ({
stop: vi.fn(), stop: vi.fn(),
})), })),
})); }));
describe("LoggingContent.vue", () => { // Test Description
describe("Test LoggingContent.vue", () => {
let wrapper; let wrapper;
// A sample log string matching backend's format // Define path to a real log file and read it
const mockLogData = `[2026-05-14 08:46:12,808] [INFO] OFM server root logger has been set up at INFO level`; const logFilePath = path.resolve(__dirname, "../fixtures/realLog.txt");
const mockLogData = fs.readFileSync(logFilePath, "utf-8");
// Things to do before each test
beforeEach(async () => { beforeEach(async () => {
// Reset mocks before each test // Reset mocks before each test
vi.clearAllMocks(); vi.clearAllMocks();
@ -43,20 +59,30 @@ describe("LoggingContent.vue", () => {
stubs: { stubs: {
PaginateLinks: true, PaginateLinks: true,
EndpointButton: true, EndpointButton: true,
transition: false, transition: true,
teleport: true, teleport: true,
settings: true,
}, },
}, },
}); });
// flush so we avoid leaving uncompleted processes running on background
await flushPromises(); await flushPromises();
}); });
// Render check // Tear down wrapper, unmount testing component
it("renders the component correctly", () => { afterEach(() => {
wrapper.unmount();
});
// Test 1: Render check, if things do exist in the page as intended
it("renders the component correctly", async () => {
// Check if component is been loaded and it is rendered
expect(wrapper.exists()).toBe(true); expect(wrapper.exists()).toBe(true);
// Check if navbar exists
expect(wrapper.find(".logging-navbar").exists()).toBe(true); expect(wrapper.find(".logging-navbar").exists()).toBe(true);
}); });
// Test 2: Check log parsing to see if specific INFO exists
it("fetches logs and parses them correctly when updateLogs is called", async () => { it("fetches logs and parses them correctly when updateLogs is called", async () => {
// Trigger the method // Trigger the method
await wrapper.vm.updateLogs(); await wrapper.vm.updateLogs();
@ -65,28 +91,60 @@ describe("LoggingContent.vue", () => {
// Verify Axios was called with the correct URI from the Pinia store // Verify Axios was called with the correct URI from the Pinia store
expect(axios.get).toHaveBeenCalledWith("http://microscope.local:5000/api/v3/log/"); expect(axios.get).toHaveBeenCalledWith("http://microscope.local:5000/api/v3/log/");
// Verify the logs array was populated and reversed (newest first) // Verify the logs have correct file length
expect(wrapper.vm.logs.length).toBe(1); // Helps validate formatting, etc
expect(wrapper.vm.logs.length).toBe(5491);
// Check if the most recent log (ERROR) is first due to .reverse() // Check the most recent log
expect(wrapper.vm.logs[0].level).toBe("INFO"); expect(wrapper.vm.logs[0].level).toBe("INFO");
// Check if the multi-line traceback was appended correctly to the ERROR log // Check if the multi-line file has real first connection information
expect(wrapper.vm.logs[0].message).toContain("OFM"); expect(wrapper.vm.logs[0].message).toContain("uvicorn");
expect(wrapper.vm.logs[0].summary).toContain("server"); expect(wrapper.vm.logs[0].summary).toContain("http://0.0.0.0:5000");
}); });
// Test 3: Filter logging file information
it("filters logs based on the selected level", async () => { it("filters logs based on the selected level", async () => {
await wrapper.vm.updateLogs(); await wrapper.vm.updateLogs();
// Set filter to ERROR (should only show ERROR and CRITICAL) // Set filter to WARNING wait and compare expected length
wrapper.vm.filterLevel = "INFO"; wrapper.vm.filterLevel = "WARNING";
// wait for Vue reactivity to update computed properties
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(wrapper.vm.filteredItems.length).toBe(1449);
expect(wrapper.vm.filteredItems[0].level).toBe("WARNING");
// Only the 1 ERROR log should remain in filteredItems // Set filter to INFO wait and compare expected length
expect(wrapper.vm.filteredItems.length).toBe(1); wrapper.vm.filterLevel = "INFO";
await wrapper.vm.$nextTick();
expect(wrapper.vm.filteredItems.length).toBe(5491);
expect(wrapper.vm.filteredItems[0].level).toBe("INFO"); expect(wrapper.vm.filteredItems[0].level).toBe("INFO");
// Set filter to ERROR wait and compare expected length
wrapper.vm.filterLevel = "ERROR";
await wrapper.vm.$nextTick();
expect(wrapper.vm.filteredItems.length).toBe(7);
expect(wrapper.vm.filteredItems[0].level).toBe("ERROR");
});
// Test 4: Click buttons
// This required the component file to be modified by adding data-test-id flags.
// flags are needed as classes and text content may change by translation or style change.
it("click buttons", async () => {
// Click download button
const downloadButton = wrapper.find('[data-test-id="download-btn"]');
expect(downloadButton.exists()).toBe(true);
await downloadButton.trigger("click");
// Click update button
const updateButton = wrapper.find('[data-test-id="update-btn"]');
expect(updateButton.exists()).toBe(true);
await updateButton.trigger("click");
});
// Test 5: Compare downloaded file to fixture file.
// Test 6: WIP
afterAll(async () => {
await new Promise((resolve) => setTimeout(resolve, 15));
}); });
}); });

View file

@ -5,16 +5,18 @@ import { defineConfig } from "vitest/config";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import path from "path"; import path from "path";
console.log("\n\n::: Reading vitest config.") console.log("\n\n::: Reading vitest config.");
export default defineConfig({ export default defineConfig({
plugins: [vue({ plugins: [
vue({
template: { template: {
compilerOptions: { compilerOptions: {
hoistStatic: false, hoistStatic: false,
}, },
}, },
})], }),
],
test: { test: {
environment: "jsdom", environment: "jsdom",
@ -31,7 +33,7 @@ export default defineConfig({
resolve: { resolve: {
alias: { alias: {
"@": path.resolve(__dirname, "./src"), "@": path.resolve(__dirname, "./src"),
"vue": path.resolve(__dirname, "./node_modules/vue"), vue: path.resolve(__dirname, "./node_modules/vue"),
}, },
}, },
}); });