Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUN-2321- Added unit test for saved Filters #9101

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
<template>
<span>
<btn
v-if="hasQuery && (!query || !query.ftilerName)"
v-if="hasQuery && (!query || !query.filterName)"
size="xs"
type="default"
data-test-id="save-filter-button"
@click="saveFilterPrompt"
>
{{ $t("filter.save.button") }}
</btn>
<span v-if="query && query.filterName">{{ query.filterName }}</span>
<span v-if="query && query.filterName" data-test-id="filter-name">{{
query.filterName
}}</span>

<dropdown v-if="filters && filters.length > 0">
<span
class="dropdown-toggle btn btn-secondary btn-sm"
:class="query && query.filterName ? 'text-info' : 'text-secondary'"
data-test-id="dropdown-toggle"
>
{{ $t("Filters") }}
<span class="caret"></span>
</span>
<template #dropdown>
<li v-if="query && query.filterName">
<a role="button" @click="deleteFilter">
<a
role="button"
data-test-id="delete-filter-btn"
@click="deleteFilter"
>
<i class="glyphicon glyphicon-trash"></i>
{{ $t("filter.delete.named.text", [query.filterName]) }}
</a>
Expand All @@ -34,16 +42,23 @@
<i class="glyphicon glyphicon-filter"></i>
{{ $t("saved.filters") }}
</li>
<li v-for="filter in filters" :key="filter.filterName">
<li
v-for="filter in filters"
:key="filter.filterName"
data-test-id="filter-item"
>
<a role="button" @click="selectFilter(filter)">
{{ filter.filterName }}
<span v-if="query && filter.filterName === query.filterName"
<span
v-if="query && filter.filterName === query.filterName"
data-test-id="checkmark-span"
>√</span
>
</a>
</li>
</template>
</dropdown>
<span v-else data-test-id="no-filters-message"> No filters available </span>
</span>
</template>
<script lang="ts">
Expand All @@ -53,7 +68,20 @@ import { getRundeckContext } from "../../../library";
import { MessageBox, Notification } from "uiv";

export default defineComponent({
props: ["query", "hasQuery", "eventBus"],
props: {
hasQuery: {
type: Boolean,
required: true,
},
query: {
type: Object,
required: true,
},
eventBus: {
type: Object,
required: true,
},
},
emits: ["select_filter"],
data() {
return {
Expand All @@ -69,6 +97,7 @@ export default defineComponent({
mounted() {
this.projectName = getRundeckContext().projectName;
this.loadFilters();

this.eventBus &&
this.eventBus.on("invoke-save-filter", this.saveFilterPrompt);
},
Expand All @@ -95,19 +124,24 @@ export default defineComponent({
if (!this.query || !this.query.filterName) {
return;
}

this.$confirm({
title: this.$t("Delete Saved Filter"),
content: this.$t("filter.delete.confirm.text", [this.query.filterName]),
})

.then(() => {
this.doDeleteFilter(this.query.filterName);
this.$emit("delete-filter", this.query.filterName);
})

.catch(() => {
//this.$notify("Delete canceled.");
});
},
async doDeleteFilter(name) {
this.filterStore.removeFilter(this.projectName, name);

await this.loadFilters();
},
async doSaveFilter(name) {
Expand All @@ -124,13 +158,13 @@ export default defineComponent({
saveFilterPrompt() {
MessageBox.prompt({
title: this.promptTitle,

content: this.promptContent,
validator(value) {
return /.+/.test(value) ? null : this.promptError;
},
})
.then((value) => {
console.log("save value", value);
this.doSaveFilter(value);
})
.catch((e) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
import { mount } from "@vue/test-utils";
import { ComponentPublicInstance } from "vue";
import SavedFilters from "../savedFilters.vue";
import { Notification, MessageBox } from "uiv";
// Custom type for the component instance
type SavedFiltersInstance = ComponentPublicInstance<{
filters: { filterName: string }[];
selectFilter: (filter: { filterName: string }) => void;
deleteFilter: () => void;
saveFilter: (filterName: string) => void;
loadFilters: () => void;
saveFilterPrompt: () => void;
notifyError: (message: string) => void;
doSaveFilter: (filterName: string) => void;
}>;
const mockDropdown = {
template: `
<div data-test-id="dropdown">
<button @click="toggleDropdown" data-test-id="toggle-button">Toggle</button>
<div v-if="isOpen">
<slot></slot>
<span data-test-id="checkmark-span">√</span>
<button data-test-id="delete-filter-btn" @click="emitDeleteFilter">Delete Filter</button>
</div>
</div>
`,
data() {
return {
isOpen: false,
};
},
methods: {
toggleDropdown() {
this.isOpen = !this.isOpen;
},
emitDeleteFilter() {
this.$emit("delete-filter");
},
},
};
const eventBus = {
on: jest.fn(),
emit: jest.fn(),
off: jest.fn(),
};
const mountSavedFilters = async (props = {}) => {
const wrapper = mount<SavedFiltersInstance>(SavedFilters, {
props: {
hasQuery: true,
query: { filterName: "test4" },
eventBus,
...props,
},
attachTo: document.body,
global: {
mocks: {
$t: (msg: string) => msg,
},
stubs: {
btn: true,
dropdown: mockDropdown,
},
},
});
await wrapper.vm.$nextTick();
return wrapper;
};
jest.mock("@/library/rundeckService", () => ({
getRundeckContext: jest.fn().mockReturnValue({ projectName: "test" }),
}));
jest.mock("../../../../library/stores/ActivityFilterStore", () => ({
ActivityFilterStore: jest.fn().mockImplementation(() => ({
loadForProject: jest.fn().mockReturnValue({
filters: [
{ filterName: "Filter 1" },
{ filterName: "Filter 2" },
{ filterName: "Filter 3" },
],
}),
saveFilter: jest.fn().mockResolvedValue({ success: true }),
removeFilter: jest.fn().mockResolvedValue(undefined),
})),
}));
jest.mock("uiv", () => ({
MessageBox: {
confirm: jest.fn().mockResolvedValue(true),
prompt: jest.fn().mockResolvedValue("new filter name"),
},
Notification: {
notify: jest.fn(),
},
}));
describe("SavedFilters", () => {
afterEach(() => {
jest.clearAllMocks();
});
describe("Component Initialization", () => {
it("should register and unregister event listeners", async () => {
const wrapper = await mountSavedFilters();
expect(eventBus.on).toHaveBeenCalledWith(
"invoke-save-filter",
expect.any(Function),
);
wrapper.unmount();
expect(eventBus.off).toHaveBeenCalledWith("invoke-save-filter");
});
});
describe("Interaction with Filters", () => {
it("renders save button conditionally based on query props", async () => {
const wrapper = await mountSavedFilters();
expect(wrapper.find('[data-test-id="save-filter-button"]').exists()).toBe(
wrapper.props().hasQuery && !wrapper.props().query.filterName,
);
});
it("emits 'select_filter' when a filter is selected", async () => {
const wrapper = await mountSavedFilters();
await wrapper.vm.selectFilter({ filterName: "Test Filter" });
expect(wrapper.emitted("select_filter")![0]).toEqual([
{ filterName: "Test Filter" },
]);
});
it("triggers delete filter action when delete button is clicked", async () => {
const wrapper = await mountSavedFilters();
const dropdownToggle = wrapper.find('[data-test-id="toggle-button"]');
await dropdownToggle.trigger("click");
await wrapper.vm.$nextTick();
const deleteButton = wrapper.find('[data-test-id="delete-filter-btn"]');
expect(deleteButton.exists()).toBe(true);
});
it("calls deleteFilter when the delete filter button is clicked", async () => {
const wrapper = await mountSavedFilters();
await wrapper.vm.$nextTick();
const dropdownToggle = wrapper.find('[data-test-id="toggle-button"]');
await dropdownToggle.trigger("click");
await wrapper.vm.$nextTick();
const deleteButton = wrapper.find('[data-test-id="delete-filter-btn"]');

await deleteButton.trigger("click");
await wrapper.vm.$nextTick();
});
it("should handle dropdown interactions and delete filter", async () => {
const wrapper = await mountSavedFilters();
const deleteFilterSpy = jest.spyOn(wrapper.vm, "deleteFilter");
const deleteButton = wrapper.find('[data-test-id="delete-filter-btn"]');
if (deleteButton.exists()) {
await deleteButton.trigger("click");

const dropdownComponent = wrapper.findComponent(mockDropdown);
expect(
dropdownComponent.emitted().hasOwnProperty("delete-filter"),
).toBe(true);
expect(deleteFilterSpy).toHaveBeenCalled(); // Check if deleteFilter method was called
}
deleteFilterSpy.mockRestore();
});
});
describe("Rendering of UI Components", () => {
it("checks for the presence of dropdown when filters exist", async () => {
const wrapper = await mountSavedFilters();
await wrapper.setData({ filters: [{ filterName: "Test Filter" }] });
await wrapper.vm.$nextTick();
expect(wrapper.find('[data-test-id="dropdown"]').exists()).toBe(true);
});
it("displays no filters message when no filters are available", async () => {
const wrapper = await mountSavedFilters();
wrapper.setData({ filters: [] });
await wrapper.vm.$nextTick();
expect(
wrapper.find('[data-test-id="no-filters-message"]').text(),
).toContain("No filters available");
});
it("renders checkmark span when query and filterName matches a filter", async () => {
const wrapper = await mountSavedFilters();
await wrapper.setData({ filters: [{ filterName: "Filter 1" }] });
await wrapper.vm.$nextTick();
const toggleButton = wrapper.find('[data-test-id="toggle-button"]');

await toggleButton.trigger("click");
await wrapper.vm.$nextTick();

const checkmarkSpan = wrapper.find('[data-test-id="checkmark-span"]');
expect(checkmarkSpan.exists()).toBe(true);
expect(checkmarkSpan.text()).toContain("√");
});
});
describe("Success and Error Handling", () => {
it("displays notification upon successful filter save", async () => {
const wrapper = await mountSavedFilters();
const saveFilterMock = jest.fn().mockImplementation(() => {
Notification.notify({ type: "success" });
});
wrapper.vm.saveFilter = saveFilterMock;
const notifySpy = jest.spyOn(Notification, "notify");
await wrapper.vm.saveFilter("New Filter");
expect(notifySpy).toHaveBeenCalledWith(
expect.objectContaining({ type: "success" }),
);
notifySpy.mockRestore();
});
it("displays error when filter saving fails", async () => {
const wrapper = await mountSavedFilters();
wrapper.vm.doSaveFilter = async function () {
throw new Error("Save failed"); // This line simulates a failure
};
const notifySpy = jest.spyOn(wrapper.vm, "notifyError");
try {
await wrapper.vm.doSaveFilter("New Filter");
} catch (error) {
wrapper.vm.notifyError(error.message);
expect(notifySpy).toHaveBeenCalledWith("Save failed");
} finally {
notifySpy.mockRestore();
}
});
});
describe("loadFilters Method", () => {
it("correctly sets filters when loadFilters is called", async () => {
const wrapper = await mountSavedFilters();
wrapper.vm.filters = [];
await wrapper.vm.loadFilters();
expect(wrapper.vm.filters).toEqual([
{ filterName: "Filter 1" },
{ filterName: "Filter 2" },
{ filterName: "Filter 3" },
]);
});
});
describe("saveFilterPrompt Method", () => {
it("triggers MessageBox.prompt and calls doSaveFilter on confirm", async () => {
const wrapper = await mountSavedFilters();
const doSaveFilterSpy = jest.spyOn(wrapper.vm, "doSaveFilter");
await wrapper.vm.saveFilterPrompt();
expect(MessageBox.prompt).toHaveBeenCalled();
await wrapper.vm.$nextTick();
expect(doSaveFilterSpy).toHaveBeenCalledWith("new filter name");
doSaveFilterSpy.mockRestore();
});
});
describe("notifyError Method", () => {
it("displays a notification with the correct message", async () => {
const wrapper = await mountSavedFilters();
const notifySpy = jest.spyOn(Notification, "notify");
wrapper.vm.notifyError("Test error message");
expect(notifySpy).toHaveBeenCalledWith({
type: "danger",
title: "An Error Occurred",
content: "Test error message",
duration: 0,
});
notifySpy.mockRestore();
});
});
});