Skip to content

Commit

Permalink
Merge pull request #9084 from rundeck/feat/unit-test-for-edit-project…
Browse files Browse the repository at this point in the history
…-file

RUN-2307 unit tests for edit project file
  • Loading branch information
ltamaster committed May 8, 2024
2 parents 05e5133 + 8e3da89 commit 1e2978e
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { mount } from "@vue/test-utils";
import EditProjectFile from "./EditProjectFile.vue";
import * as editProjectFileService from "./editProjectFileService";

jest.mock("@/app/components/readme-motd/editProjectFileService", () => ({
getFileText: jest.fn().mockResolvedValue({
success: true,
contents: "sample file content",
}),
saveProjectFile: jest.fn().mockResolvedValue({
success: true,
message: "File saved successfully.",
}),
}));

jest.mock("@/library/rundeckService", () => ({
getRundeckContext: jest.fn().mockReturnValue({
rundeckClient: {
getFileText: jest.fn().mockResolvedValue({
filename: "readme.md",
success: true,
contents: "Some content",
rdBase: "http://localhost:4440",
}),
saveProjectFile: jest.fn().mockResolvedValue({
success: true,
message: "File saved successfully.",
}),
},
rdBase: "http://localhost:4440",
}),
url: jest.fn().mockReturnValue({
href: "http://localhost:4440/project/default/home",
}),
}));

jest.mock("../../../library/components/utils/AceEditor.vue", () => ({
name: "AceEditor",
functional: true,
template: '<span class="ace_text ace_xml"></span>',
methods: {
getValue: jest.fn().mockReturnValue("sample file content"),
},
}));

describe("EditProjectFile", () => {
let wrapper;
let originalLocation;

const mountEditProjectFile = async (props = {}) => {
wrapper = mount(EditProjectFile, {
props: {
filename: "readme.md",
project: "default",
authAdmin: true,
displayConfig: ["none"],
...props,
},
global: {
mocks: {
$t: (msg) => msg,
},
},
});
await wrapper.vm.$nextTick();
};

beforeEach(async () => {
originalLocation = window.location;
delete (window as any).location;
window.location = {
...originalLocation,
assign: jest.fn(),
href: jest.fn(),
replace: jest.fn(),
toString: () => (window.location as any)._href || "http://localhost:4440",
};

await mountEditProjectFile();
});

afterEach(() => {
jest.clearAllMocks();
window.location = originalLocation;
});

it.each([
["readme.md", "edit.readme.label"],
["motd.md", "edit.motd.label"],
])("renders the correct title for %s", async (filename, expectedTitle) => {
await mountEditProjectFile({ filename });
expect(wrapper.find('[data-test-id="title"]').text()).toContain(
expectedTitle
);
});
it("renders file content inside AceEditor's span element when getFileText method returns successfully", async () => {
wrapper.vm.getFileText = jest.fn().mockResolvedValue("sample file content");
await wrapper.vm.getFileText();
await wrapper.vm.$nextTick();
const aceEditor = wrapper.findComponent({ name: "AceEditor" });
expect(aceEditor.exists()).toBe(true);
const span = aceEditor.find("span.ace_text.ace_xml");
expect(span.exists()).toBe(true);
const spanHtml = span.html();
expect(spanHtml).toContain("sample file content");
});
it("handles failure when getFileText method fails", async () => {
const notifyErrorSpy = jest.spyOn(wrapper.vm, "notifyError");
(editProjectFileService.getFileText as jest.Mock).mockImplementationOnce(
() => Promise.reject(new Error("Failed to fetch file"))
);
await wrapper.vm.getFileText();
expect(notifyErrorSpy).toHaveBeenCalledWith("Failed to fetch file");
});

it("handles failure when user edits the file and fails to save it", async () => {
(
editProjectFileService.saveProjectFile as jest.Mock
).mockImplementationOnce(() =>
Promise.reject(new Error("Failed to save file"))
);
const notifyErrorSpy = jest.spyOn(wrapper.vm, "notifyError");
wrapper.vm.fileText = "new content";
await wrapper.find('[data-test-id="save"]').trigger("click");
expect(notifyErrorSpy).toHaveBeenCalledWith("Failed to save file");
});
it("handles success when user edits the file and saves it", async () => {
wrapper.vm.fileText = "new content";
await wrapper.find('[data-test-id="save"]').trigger("click");
expect(
editProjectFileService.saveProjectFile as jest.Mock
).toHaveBeenCalledWith("default", "readme.md", "new content");
});

it("displays warning message and configuration link when user is an admin and displayConfig value is 'none", async () => {
const footerText = wrapper.find(".card-footer").text();
expect(footerText).toContain("file.warning.not.displayed.admin.message");
expect(wrapper.find(".card-footer a").text()).toBe(
"project.configuration.label"
);
});

it("displays warning message and configuration link when user isn't an admin and displayConfig value is 'none", async () => {
await mountEditProjectFile({
authAdmin: false,
});
expect(
wrapper.find('[data-test-id="nonadmin-warning-message"]').text()
).toContain("file.warning.not.displayed.nonadmin.message");
});
it("navigates to the home page when the cancel button is clicked", async () => {
await mountEditProjectFile();
const button = wrapper.find('[data-test-id="cancel"]');

await button.trigger("click");

expect(window.location).toBe("http://localhost:4440/project/default/home");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="content">
<div id="layoutBody">
<div class="title">
<span class="text-h3">
<span class="text-h3" data-test-id="title">
<template v-if="filename === 'readme.md'">
<i class="fas fa-file-alt"></i>
{{ $t("edit.readme.label") }}
Expand Down Expand Up @@ -45,31 +45,38 @@
</details>
</div>
<ace-editor
ref="aceEditor"
v-model="fileText"
:soft-wrap-control="true"
height="250"
width="100%"
lang="markdown"
:read-only="false"
data-test-id="ace-editor"
/>
</div>
<div class="card-footer">
<button
type="button"
class="btn btn-default reset_page_confirm"
data-test-id="cancel"
@click="createProjectHomeLink"
>
Cancel
</button>
<button
type="submit"
class="btn btn-cta reset_page_confirm"
data-test-id="save"
@click="saveProjectFile"
>
Save
</button>
<template v-if="displayConfig.includes('none')">
<span class="text-warning text-right">
<span
class="text-warning text-right"
data-test-id="nonadmin-warning-message"
>
<template v-if="authAdmin">
{{ $t("file.warning.not.displayed.admin.message") }}
<a :href="createProjectConfigureLink">
Expand Down Expand Up @@ -127,6 +134,7 @@ export default defineComponent({
data() {
return {
fileText: "",
markdownSectionOpen: false,
errorMsg: "",
};
Expand All @@ -145,7 +153,7 @@ export default defineComponent({
const resp = await saveProjectFile(
this.project,
this.filename,
this.fileText,
this.fileText
);
if (resp.success) {
this.notifySuccess("Success", "Saved Project File " + this.filename);
Expand All @@ -155,7 +163,7 @@ export default defineComponent({
}
},
createProjectHomeLink() {
document.location = url("project/" + this.project + "/home").href;
window.location = url("project/" + this.project + "/home").href;
},
notifyError(msg) {
Notification.notify({
Expand Down Expand Up @@ -185,6 +193,7 @@ export default defineComponent({
async getFileText() {
try {
const response = await getFileText(this.project, this.filename);
if (response.success) {
this.fileText = response.contents;
}
Expand All @@ -195,7 +204,7 @@ export default defineComponent({
this.filename +
" does not exist in project " +
this.project +
" yet. Please save to create it.",
" yet. Please save to create it."
);
} else {
this.notifyError(e.message);
Expand Down

0 comments on commit 1e2978e

Please sign in to comment.