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

N21-2052 adjust ctl autocomplete #3313

Merged
merged 30 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ea07696
N21-2052 generate api, adds baseUrl and change autocomplete wip
arnegns Jul 3, 2024
3978a27
added check for valid url & hide "no data" when url is inputted
GordonNicholasCap Jul 3, 2024
16f5116
Merge branch 'refs/heads/main' into N21-2052-ctl-url-insertion
arnegns Jul 3, 2024
b8613a5
Merge branch 'refs/heads/main' into N21-2052-ctl-url-insertion
arnegns Jul 3, 2024
26820ca
N21-2052 some logic
arnegns Jul 3, 2024
99c51eb
N21-2052 fixes escaping
arnegns Jul 4, 2024
c1ed9ce
N21-2052 fixes test
arnegns Jul 4, 2024
92f7efa
N21-2052 changes ExternalToolSelectionRow.vue to script setup
arnegns Jul 4, 2024
cd373b2
N21-2052 some changes
arnegns Jul 4, 2024
9a040a8
N21-2052 adds clipboard logic
arnegns Jul 4, 2024
6938535
N21-2052 fixes linter error
arnegns Jul 4, 2024
8872d6d
fixed settings still showing when box is cleared, minor cleanup
GordonNicholasCap Jul 4, 2024
1c26507
moved code for url into composable, added query param extractor
GordonNicholasCap Jul 4, 2024
eaa0728
added hint below combobox, changed paste url icon
GordonNicholasCap Jul 5, 2024
e22917c
adjusted valid url regex, fixed minor bugs in composable
GordonNicholasCap Jul 5, 2024
b538fa6
wip unit tests for configurator & composable
GordonNicholasCap Jul 5, 2024
e7368bb
full unit tests for composable
GordonNicholasCap Jul 8, 2024
abe2ccb
refactored script -> script setup, fixed missing baseUrl
GordonNicholasCap Jul 8, 2024
feb15a2
cleanup unit tests, skip tests involving combobox's selection menu
GordonNicholasCap Jul 9, 2024
7b54bc9
added proper error handling for clipboard, adjusted code for checking…
GordonNicholasCap Jul 9, 2024
4109dcd
Merge branch 'refs/heads/main' into N21-2052-ctl-url-insertion
GordonNicholasCap Jul 9, 2024
c73dc00
fixed tests
GordonNicholasCap Jul 9, 2024
73806b5
make search box testable
MarvinOehlerkingCap Jul 10, 2024
e204700
N21-2052 refactored composable unit tests to be simpler
GordonNicholasCap Jul 10, 2024
5439fa4
N21-2052 renamed comboboxRef, added test for selection list filter
GordonNicholasCap Jul 10, 2024
a9b34c5
remove v-if from slot
MarvinOehlerkingCap Jul 11, 2024
5e58111
change order of execution for paste-icon
MarvinOehlerkingCap Jul 11, 2024
f2cfc93
minor adjustments, fixed typos
GordonNicholasCap Jul 11, 2024
8c9716b
Merge branch 'main' into N21-2052-ctl-url-insertion
GordonNicholasCap Jul 11, 2024
9bf5b83
fix focus and accessibility for past-icon
MarvinOehlerkingCap Jul 11, 2024
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
Expand Up @@ -22,6 +22,9 @@ import { createMock, DeepMocked } from "@golevelup/ts-jest";
import { mount } from "@vue/test-utils";
import { nextTick, ref } from "vue";
import ContextExternalToolConfigurator from "./ContextExternalToolConfigurator.vue";
import { createModuleMocks } from "@/utils/mock-store-module";
import NotifierModule from "@/store/notifier";
import { NOTIFIER_MODULE_KEY } from "@/utils/inject";

jest.mock(
"@data-external-tool/contextExternalToolConfigurationState.composable"
Expand All @@ -39,9 +42,14 @@ describe("CourseContextExternalToolConfigurator", () => {
const getWrapper = (
props: ComponentProps<typeof ContextExternalToolConfigurator>
) => {
const notifierModule = createModuleMocks(NotifierModule);

const wrapper = mount(ContextExternalToolConfigurator, {
global: {
plugins: [createTestingVuetify(), createTestingI18n()],
provide: {
[NOTIFIER_MODULE_KEY.valueOf()]: notifierModule,
},
},
props,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import * as useExternalToolUtilsComposable from "@/composables/external-tool-mappings.composable";
import { SchoolExternalTool } from "@/store/external-tool";
import { BusinessError } from "@/store/types/commons";
import { ToolParameterLocation } from "@/store/external-tool";
import NotifierModule from "@/store/notifier";
import { ComponentProps } from "@/types/vue";
import { NOTIFIER_MODULE_KEY } from "@/utils/inject";
import { createModuleMocks } from "@/utils/mock-store-module";
import {
schoolExternalToolConfigurationTemplateFactory,
schoolExternalToolFactory,
toolParameterFactory,
} from "@@/tests/test-utils/factory";
import {
createTestingI18n,
createTestingVuetify,
} from "@@/tests/test-utils/setup";
import {
ContextExternalTool,
ExternalToolConfigurationTemplate,
} from "@data-external-tool";
import { mount, VueWrapper } from "@vue/test-utils";
import { VBtn } from "vuetify/lib/components/index.mjs";
import { createMock } from "@golevelup/ts-jest";
import { flushPromises, mount } from "@vue/test-utils";
import { VAutocomplete, VBtn } from "vuetify/lib/components/index.mjs";
import ExternalToolConfigSettings from "./ExternalToolConfigSettings.vue";
import ExternalToolConfigurator from "./ExternalToolConfigurator.vue";

describe("ExternalToolConfigurator", () => {
Expand All @@ -25,15 +27,17 @@ describe("ExternalToolConfigurator", () => {
getBusinessErrorTranslationKey: () => "",
});

const getWrapper = (props: {
templates: ExternalToolConfigurationTemplate[];
configuration?: SchoolExternalTool | ContextExternalTool;
error?: BusinessError;
loading?: boolean;
}) => {
const getWrapper = (
props: ComponentProps<typeof ExternalToolConfigurator>
) => {
const notifierModule = createModuleMocks(NotifierModule);

const wrapper = mount(ExternalToolConfigurator, {
global: {
plugins: [createTestingVuetify(), createTestingI18n()],
provide: {
[NOTIFIER_MODULE_KEY.valueOf()]: notifierModule,
},
},
props,
});
Expand All @@ -47,91 +51,158 @@ describe("ExternalToolConfigurator", () => {
jest.clearAllMocks();
});

describe("autocomplete", () => {
describe("when selecting a new configuration", () => {
describe("Search box", () => {
describe("when editing a configuration", () => {
const setup = () => {
const template = schoolExternalToolConfigurationTemplateFactory.build({
logoUrl: "some logo",
});
const template = schoolExternalToolConfigurationTemplateFactory.build();

const { wrapper } = getWrapper({
templates: [template],
configuration: schoolExternalToolFactory.build(),
});

const openSelect = async (wrapper: VueWrapper) => {
await wrapper
.find('[data-testId="configuration-select"]')
.trigger("click");

await wrapper
.find(".menuable__content__active .v-list-item:firstChild")
.trigger("click");
};

return {
wrapper,
template,
openSelect,
};
};

// TODO: comment in the test when https://github.com/vuetifyjs/vuetify/pull/16272 is merged
it.skip("should display name and logo of an tool configuration in selection list", async () => {
const { wrapper, template, openSelect } = setup();

await openSelect(wrapper);
it("should disable the selection", async () => {
const { wrapper } = setup();

const selectionRow = wrapper.find(".row");
const select = wrapper
.findComponent('[data-testId="configuration-select"]')
.get("input");

expect(selectionRow.find(".v-image__image").exists()).toBeTruthy();
expect(selectionRow.find("span").text()).toEqual(
expect.stringContaining(template.name)
);
expect(select.attributes("disabled")).toBeDefined();
});

// TODO: comment in the test when https://github.com/vuetifyjs/vuetify/pull/16272 is merged
it.skip("should set enable the save button", async () => {
const { wrapper, openSelect } = setup();

await openSelect(wrapper);
it("should display the edited tool in the selection", async () => {
const { wrapper, template } = setup();

const button = wrapper.find('[data-testId="save-button"]');
const selectionRow = wrapper.find(".v-autocomplete .v-list-item-title");

expect(button.attributes("disabled")).toBeUndefined();
expect(selectionRow.text()).toEqual(template.name);
});
});

describe("when editing a configuration", () => {
describe("when clicking on the 'paste' icon", () => {
const setup = () => {
const clipboardText = "https://google.de";
const template = schoolExternalToolConfigurationTemplateFactory.build();

const clipboardMock = createMock<Clipboard>();
Object.assign(navigator, { clipboard: clipboardMock });

const { wrapper } = getWrapper({
templates: [template],
configuration: schoolExternalToolFactory.build(),
});

clipboardMock.readText.mockResolvedValue(clipboardText);

return {
wrapper,
template,
clipboardMock,
clipboardText,
};
};

it("should disable the selection", async () => {
const { wrapper } = setup();
it("should paste the text from the clipboard into the input field", async () => {
const { wrapper, clipboardMock, clipboardText } = setup();

const select = wrapper
.findComponent('[data-testId="configuration-select"]')
.get("input");
const icon = wrapper
.find(".v-input__append")
.find(".v-icon--clickable");
await icon.trigger("click");
await flushPromises();

expect(select.attributes("disabled")).toBeDefined();
const autocomplete = wrapper.findComponent(VAutocomplete);

expect(clipboardMock.readText).toHaveBeenCalled();
expect(autocomplete.props().search).toEqual(clipboardText);
});
});

it("should display the edited tool in the selection", async () => {
describe("when pasting a url for an existing tool into the search box", () => {
beforeEach(() => {
const vueTeleportDiv = document.createElement("div");
vueTeleportDiv.className = "v-overlay-container";
document.body.appendChild(vueTeleportDiv);
});

afterEach(() => {
document.body.innerHTML = "";
});

const setup = () => {
const template = schoolExternalToolConfigurationTemplateFactory.build({
baseUrl: "https://test.com/:pathParam1/spacer/:pathParam2",
parameters: [
toolParameterFactory.build({
name: "pathParam1",
location: ToolParameterLocation.PATH,
}),
toolParameterFactory.build({
name: "queryParam1",
location: ToolParameterLocation.QUERY,
}),
toolParameterFactory.build({
name: "pathParam2",
location: ToolParameterLocation.PATH,
}),
],
});

const { wrapper } = getWrapper({
templates: [
template,
schoolExternalToolConfigurationTemplateFactory.build(),
],
});

return {
wrapper,
template,
};
};

it("should only show the matched tool in the selection list", async () => {
const { wrapper, template } = setup();

const selectionRow = wrapper.find(".v-autocomplete .v-list-item-title");
await wrapper.find(".v-field__input").trigger("click");

expect(selectionRow.text()).toEqual(template.name);
const searchInput = wrapper
.find('[data-testId="configuration-select"]')
.find("input");
await searchInput.setValue(
"https://test.com/pathParamValue1/spacer/pathParamValue2?queryParam1=queryParamValue1"
);

const selectionItems = wrapper.findAllComponents(
'[data-testid="configuration-select-item"]'
);

expect(selectionItems.length).toEqual(1);
expect(selectionItems[0].text()).toEqual(template.name);
});

it("should automatically fill the parameters", async () => {
const { wrapper, template } = setup();

const autocomplete = wrapper.findComponent(VAutocomplete);
await autocomplete.setValue(
"https://test.com/pathParamValue1/spacer/pathParamValue2?queryParam1=queryParamValue1",
"search"
);
await autocomplete.setValue(template);

const settings = wrapper.getComponent(ExternalToolConfigSettings);
expect(settings.props().modelValue).toEqual([
"pathParamValue1",
"queryParamValue1",
"pathParamValue2",
]);
});
});
});
Expand Down
Loading
Loading