Skip to content

Commit

Permalink
[studio] enable to change the password #1488 (#1504)
Browse files Browse the repository at this point in the history
* studio - user dropdown

* studio - modals improved

* studio - modal actions

* studio - change password

* studio - unit tests

* studio - unit tests

* studio - removed console log and comments
  • Loading branch information
janavlachova authored Jan 18, 2025
1 parent 68f07ff commit 46d2a95
Show file tree
Hide file tree
Showing 32 changed files with 950 additions and 254 deletions.
5 changes: 5 additions & 0 deletions agdb_studio/src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@
--blue-2: #007bff;
--blue-light: #4fcbff;
--blue-dark: #0460bf;

--z-index-modal: 1000;
--z-index-notification: 10;
--z-index-notification-new: 1100;
--z-index-dropdown: 400;
}

:root {
Expand Down
147 changes: 100 additions & 47 deletions agdb_studio/src/components/base/content/AgdbContent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,21 @@ import { describe, beforeEach, vi, it, expect } from "vitest";
import AgdbContent from "./AgdbContent.vue";
import { mount } from "@vue/test-utils";
import { useContentInputs } from "@/composables/content/inputs";
import { ref } from "vue";

const { addInput, getInputValue, clearAllInputs } = useContentInputs();
const { addInput, getInputValue, clearAllInputs, checkInputsRules } =
useContentInputs();

const testInput: Input = {
key: "testKey",
label: "Test label",
type: "text",
autofocus: true,
required: true,
value: "Test value",
};

describe("AgdbContent", () => {
const testKey = Symbol("test");
const testContentKey = Symbol("test");
beforeEach(() => {
vi.clearAllMocks();
clearAllInputs();
Expand All @@ -28,93 +37,137 @@ describe("AgdbContent", () => {
component: "my-component" as unknown as AsyncComponent,
},
{
input: {
key: "test",
type: "text",
label: "Test input",
},
input: testInput,
},
],
contentKey: testKey,
contentKey: testContentKey,
},
});
expect(wrapper.html()).toContain("Test Body");
expect(wrapper.html()).toContain("my-component");
expect(wrapper.html()).toContain("Test input");
expect(wrapper.html()).toContain("Test label");
});
it("change the input value on user input", async () => {
const inputValue = ref("");
addInput(testKey, "test", inputValue);
addInput(testContentKey, testInput);
const wrapper = mount(AgdbContent, {
props: {
content: [
{
input: {
key: "test",
type: "text",
label: "Test input",
},
input: testInput,
},
],
contentKey: testKey,
contentKey: testContentKey,
},
});
const input = wrapper.find("input");
expect(getInputValue(testKey, "test")).toBe("");
expect(input.element.value).toBe("");
input.element.value = "test value";
expect(getInputValue(testContentKey, testInput.key)).toBe("Test value");
expect(input.element.value).toBe("Test value");
input.element.value = "test value 2";
await input.trigger("input");
expect(getInputValue(testKey, "test")).toBe("test value");
expect(getInputValue(testContentKey, testInput.key)).toBe(
"test value 2",
);
});

it("sets focus on the input with autofocus", async () => {
const inputValue = ref("");
addInput(testKey, "test", inputValue);
addInput(testContentKey, testInput);
const wrapper = mount(AgdbContent, {
props: {
content: [
{
input: {
key: "test",
type: "text",
label: "Test input",
autofocus: true,
},
input: testInput,
},
],
contentKey: testKey,
contentKey: testContentKey,
},
attachTo: document.body,
});
await wrapper.vm.$nextTick();
const input = wrapper.find("input");
expect(input.element.matches(":focus")).toBe(true);
});

it("should not set focus on the input without autofocus", async () => {
const inputWithoutFocus: Input = {
key: "testKey",
label: "Test label",
type: "text",
required: true,
value: "Test value",
};
addInput(testContentKey, inputWithoutFocus);
const wrapper = mount(AgdbContent, {
props: {
content: [
{
input: inputWithoutFocus,
},
],
contentKey: testContentKey,
},
attachTo: document.body,
});
await wrapper.vm.$nextTick();
const input = wrapper.find("input");
expect(input.element.matches(":focus")).toBe(false);
});

it("should render select input and change value", async () => {
const inputValue = ref("test");
addInput(testKey, "test", inputValue);
const selectInput: Input = {
key: "testKey",
label: "Test input",
type: "select",
options: [
{ value: "test-option", label: "Test" },
{ value: "test-option-2", label: "Test2" },
],
value: "test-option",
};
addInput(testContentKey, selectInput);
const wrapper = mount(AgdbContent, {
props: {
content: [
{
input: {
key: "test",
type: "select",
label: "Test input",
options: [
{ value: "test", label: "Test" },
{ value: "test2", label: "Test2" },
],
},
input: selectInput,
},
],
contentKey: testKey,
contentKey: testContentKey,
},
});
const select = wrapper.find("select");
expect(select.element.value).toBe("test");
expect(getInputValue(testKey, "test")).toBe("test");
select.element.value = "test2";
expect(select.element.value).toBe("test-option");
expect(getInputValue(testContentKey, selectInput.key)).toBe(
"test-option",
);
select.element.value = "test-option-2";
await select.trigger("change");
expect(getInputValue(testKey, "test")).toBe("test2");
expect(getInputValue(testContentKey, selectInput.key)).toBe(
"test-option-2",
);
});

it("should display error message when input rules are false", async () => {
const requiredInput: Input = {
key: "testKey",
label: "Test input",
type: "text",
required: true,
value: "",
rules: [() => "required"],
};
addInput(testContentKey, requiredInput);
const wrapper = mount(AgdbContent, {
props: {
content: [
{
input: requiredInput,
},
],
contentKey: testContentKey,
},
});
checkInputsRules(testContentKey);
await wrapper.vm.$nextTick();
expect(wrapper.text()).toContain("required");
});
});
141 changes: 98 additions & 43 deletions agdb_studio/src/components/base/content/AgdbContent.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts" setup>
import { onMounted, ref, type PropType } from "vue";
import { useContentInputs } from "@/composables/content/inputs";
import FadeTransition from "@/components/transitions/FadeTransition.vue";
const props = defineProps({
content: { type: Array as PropType<Content[]>, required: true },
Expand Down Expand Up @@ -34,49 +35,60 @@ onMounted(() => {
<component :is="part.component" />
</div>
<div v-if="part.input" class="input-row">
<label>{{ part.input.label }}</label>
<select
v-if="
inputs.get(part.input.key) !== undefined &&
part.input.type === 'select'
"
@change="
(event: Event) => {
setInputValue(
props.contentKey,
part.input?.key,
(event.target as HTMLSelectElement).value,
);
}
"
:value="getInputValue(props.contentKey, part.input.key)"
>
<option
v-for="(option, index) in part.input.options"
:key="index"
:value="option.value"
<label :for="part.input.key">{{ part.input.label }}</label>
<div :class="{ 'error-input': part.input.error }">
<select
v-if="
inputs.get(part.input.key) !== undefined &&
part.input.type === 'select'
"
@change="
(event: Event) => {
setInputValue(
props.contentKey,
part.input?.key,
(event.target as HTMLSelectElement).value,
);
}
"
:value="getInputValue(props.contentKey, part.input.key)"
:name="part.input.key"
>
{{ option.label }}
</option>
</select>
<input
v-else-if="inputs.get(part.input.key) !== undefined"
:type="part.input.type"
:ref="
(el) => {
if (part.input?.autofocus) autofocusElement = el;
}
"
@input="
(event: Event) => {
setInputValue(
props.contentKey,
part.input?.key,
(event.target as HTMLInputElement).value,
);
}
"
/>
<option
v-for="(option, index) in part.input.options"
:key="index"
:value="option.value"
>
{{ option.label }}
</option>
</select>
<input
v-else-if="inputs.get(part.input.key) !== undefined"
:name="part.input.key"
:type="part.input.type"
:ref="
(el) => {
if (part.input?.autofocus)
autofocusElement = el;
}
"
@input="
(event: Event) => {
setInputValue(
props.contentKey,
part.input?.key,
(event.target as HTMLInputElement).value,
);
}
"
:value="getInputValue(props.contentKey, part.input.key)"
/>
<FadeTransition>
<div v-if="part.input.error" class="error-message">
{{ part.input.error }}
</div>
</FadeTransition>
</div>
</div>
</div>
</div>
Expand All @@ -90,8 +102,51 @@ onMounted(() => {
}
.input-row {
display: grid;
grid-template-columns: minmax(60px, 150px) minmax(150px, 1fr);
grid-template-columns: minmax(60px, 170px) minmax(150px, 1fr);
grid-gap: 1rem;
margin-bottom: 1rem;
position: relative;
label {
justify-self: end;
align-self: center;
}
input,
select {
width: 100%;
padding: 0.2rem;
border: 1px solid #ccc;
border-radius: 5px;
transition: outline 0.3s ease;
}
.error-input {
input,
select {
outline: 2px solid var(--red);
}
}
}
.error-message {
font-size: 0.8rem;
position: absolute;
bottom: -0.5rem;
right: 0;
background-color: var(--red-2);
color: var(--white);
border: 1px solid var(--red);
padding: 0.1rem 0.5rem;
border-radius: 5px;
z-index: 1;
max-width: 40%;
}
@media (max-width: 768px) {
.input-row {
grid-template-columns: 1fr;
}
.error-message {
max-width: 60%;
}
}
</style>
Loading

0 comments on commit 46d2a95

Please sign in to comment.