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

[WIP] Form elements refactors #17921

Draft
wants to merge 36 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2d59483
🛠️: refactor `FormHidden` to composition API and `typeScript`
itisAliRH Apr 5, 2024
dd32344
🛠️: refactor `FormHidden` test to typescript
itisAliRH Apr 5, 2024
4788781
🛠️: refactor `FormInput` to composition API and `typeScript`
itisAliRH Apr 5, 2024
5ece53c
🛠️: refactor `FormInput` test to typescript
itisAliRH Apr 5, 2024
6e7d1fa
🛠️: refactor `FormRadio` to `typeScript` and import missing components
itisAliRH Apr 5, 2024
fe3b212
🛠️: refactor `FormRadio` test to typescript
itisAliRH Apr 5, 2024
218a136
🛠️: refactor `FormRulesEdit` to `typeScript` and import missing compo…
itisAliRH Apr 5, 2024
f564b20
🛠️: refactor `FormElement` and import missing components
itisAliRH Apr 8, 2024
4d41ea4
🛠️: refactor `FormDirectory` to `typeScript` and import missing compo…
itisAliRH Apr 8, 2024
4f1e16a
🛠️: refactor `FormDirectory` test to typescript
itisAliRH Apr 8, 2024
4d69ec1
🛠️: refactor `FormNumber` to composition API and `typeScript`
itisAliRH Apr 8, 2024
11315d5
🛠️: refactor `FormNumber` test to typescript
itisAliRH Apr 8, 2024
d8ab05d
🛠️: refactor `FormOptionalText` to composition API and `typeScript`
itisAliRH Apr 8, 2024
a49d137
🛠️: refactor `FormOptionalText` test to typescript
itisAliRH Apr 8, 2024
039a077
🛠️: refactor `FormSelection` to `typeScript`
itisAliRH Apr 8, 2024
1b72221
🛠️: refactor `FormSelect` test to typescript
itisAliRH Apr 8, 2024
6465597
🛠️: refactor `FormTags` to composition API and `typeScript`
itisAliRH Apr 8, 2024
2345698
🛠️: refactor `FormText` to composition API and `typeScript` and impor…
itisAliRH Apr 8, 2024
e850518
🛠️: refactor `FormText` test to typescript
itisAliRH Apr 8, 2024
425396e
🛠️: refactor `FormUpload` to `typeScript`
itisAliRH Apr 8, 2024
6d13116
🛠️: refactor `FormUpload` test to typescript
itisAliRH Apr 8, 2024
185d42a
🛠️: fix import component `FormDrilldown` in `FormElement`
itisAliRH Apr 9, 2024
b2906ff
🛠️: update `FormUpload` prop type
itisAliRH Apr 9, 2024
4444068
🛠️: import missing bootstrap components in `FormBoolean`
itisAliRH Apr 10, 2024
2d0ad92
🛠️: refactor `FormBoolean` test to typescript
itisAliRH Apr 10, 2024
f788b87
🛠️: import missing bootstrap components in `FormColor`
itisAliRH Apr 10, 2024
4db89aa
🛠️: update `FormCheck` component to use generic type for value and im…
itisAliRH Apr 11, 2024
8004400
🛠️: refactor `FormCheck` test to typescript
itisAliRH Apr 11, 2024
71751db
🛠️: import missing bootstrap components in `FormDataDialog`
itisAliRH Apr 11, 2024
c5316e7
🛠️: update `FormInput` value prop type to accept string or number
itisAliRH Apr 11, 2024
a3bdad0
✔️: fix `FormNumber` test error
itisAliRH Apr 11, 2024
9e285ed
✔️: fix `FormOptionalText` test error
itisAliRH Apr 11, 2024
e84658b
🐛: fix `FormText` test type error
itisAliRH Apr 16, 2024
1bf0113
🛠️: rename `FormSelect.test` to `FormSelection.test` and update defau…
itisAliRH Apr 19, 2024
f7ddf49
🛠️: import missing bootstrap components on `FormSelect`and update types
itisAliRH Apr 19, 2024
3f4d02c
🛠️: update `FormSelection` component to accept number values in `valu…
itisAliRH Apr 19, 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
35 changes: 0 additions & 35 deletions client/src/components/Form/Elements/FormBoolean.test.js

This file was deleted.

38 changes: 38 additions & 0 deletions client/src/components/Form/Elements/FormBoolean.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { getLocalVue } from "@tests/jest/helpers";
import { mount, Wrapper } from "@vue/test-utils";

import FormBoolean from "./FormBoolean.vue";

const localVue = getLocalVue();

describe("FormBoolean", () => {
let wrapper: Wrapper<Vue>;

beforeEach(() => {
wrapper = mount(FormBoolean as object, {
propsData: {
value: false,
},
localVue,
});
});

it("check initial value and value change", async () => {
const input = wrapper.find("input");

await wrapper.setProps({ value: "true" });
expect(wrapper.emitted()?.input?.[0]?.[0]).toBe(true);

await wrapper.setProps({ value: "false" });
expect(wrapper.emitted()?.input?.[1]?.[0]).toBe(false);

await wrapper.setProps({ value: true });
expect(wrapper.emitted()?.input?.[2]?.[0]).toBe(true);

await input.setChecked(false);
expect(wrapper.emitted()?.input?.[3]?.[0]).toBe(false);

await input.setChecked(true);
expect(wrapper.emitted()?.input?.[4]?.[0]).toBe(true);
});
});
12 changes: 7 additions & 5 deletions client/src/components/Form/Elements/FormBoolean.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<script setup lang="ts">
import { BFormCheckbox } from "bootstrap-vue";
import { computed } from "vue";

export interface FormBooleanProps {
value: boolean | string;
export interface Props {
value: boolean | "true" | "false";
}

const props = defineProps<FormBooleanProps>();
const props = defineProps<Props>();

const emit = defineEmits<{
(e: "input", value: boolean): void;
}>();
Expand All @@ -23,7 +25,7 @@ const label = computed(() => (currentValue.value ? "Yes" : "No"));
</script>

<template>
<b-form-checkbox v-model="currentValue" class="no-highlight" switch>
<BFormCheckbox v-model="currentValue" class="no-highlight" switch>
{{ label }}
</b-form-checkbox>
</BFormCheckbox>
</template>
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { mount } from "@vue/test-utils";
import { getLocalVue } from "tests/jest/helpers";
import { getLocalVue } from "@tests/jest/helpers";
import { mount, Wrapper } from "@vue/test-utils";
import flushPromises from "flush-promises";

import MountTarget from "./FormCheck";
import MountTarget from "./FormCheck.vue";

const localVue = getLocalVue(true);

describe("FormCheck", () => {
let wrapper;
let wrapper: Wrapper<Vue>;

beforeEach(() => {
wrapper = mount(MountTarget, {
wrapper = mount(MountTarget as object, {
propsData: {
value: null,
value: [],
options: [],
},
localVue,
Expand All @@ -20,83 +21,118 @@ describe("FormCheck", () => {

it("Confirm 'n + 1' checkboxes created (eg. includes the Select-All). Confirm labels and values match. Confirm correct values emitted.", async () => {
const noInput = wrapper.find("[type='checkbox']");

expect(noInput.exists()).toBe(false);

const n = 3;
const options = [];

for (let i = 0; i < n; i++) {
options.push({ label: `label_${i}`, value: `value_${i}` });
}

await wrapper.setProps({ options });

const inputs = wrapper.findAll("[type='checkbox']");
const labels = wrapper.findAll(".custom-control-label");
expect(inputs.length).toBe(n + 1);
let expectedValues = [];
expect(labels.length).toBe(n + 1);

let expectedValues: string[] | undefined | null = [];

for (let i = 0; i < n; i++) {
await inputs.at(i + 1).setChecked();

expect(labels.at(i + 1).text()).toBe(`label_${i}`);
expect(inputs.at(i + 1).attributes("value")).toBe(`value_${i}`);

expectedValues.push(`value_${i}`);
expect(wrapper.emitted()["input"][i][0]).toEqual(expectedValues);

await flushPromises();

expect(wrapper.emitted()).toHaveProperty("input");
expect(wrapper.emitted()["input"]?.[i]?.[0]).toEqual(expectedValues);
}

for (let i = 0; i < n; i++) {
await inputs.at(i + 1).setChecked(false);
expectedValues = expectedValues.slice(1);
if (expectedValues.length === 0) {

expectedValues = expectedValues?.slice(1);

if (expectedValues?.length === 0) {
expectedValues = null;
}
expect(wrapper.emitted().input[i + 3][0]).toEqual(expectedValues);

expect(wrapper.emitted().input?.[i + 3]?.[0]).toEqual(expectedValues);
}
});

it("Confirm checkboxes are created when various 'empty values' are passed.", async () => {
const emptyValues = [0, null, false, true, undefined];
const options = [];

for (let i = 0; i < emptyValues.length; i++) {
options.push({ label: `label_${i}`, value: emptyValues[i] });
}

await wrapper.setProps({ options });

const inputs = wrapper.findAll("[type='checkbox']");
expect(inputs.length).toBe(emptyValues.length + 1);

const expectedValues = [];
for (let i = 0; i < emptyValues; i++) {

for (let i = 0; i < emptyValues.length; i++) {
await inputs.at(i + 1).setChecked();

expect(inputs.at(i + 1).attributes("value")).toBe(emptyValues[i]);
expectedValues.push(expectedValues[i]);
expect(wrapper.emitted()["input"][i][0]).toEqual(expectedValues);
expectedValues.push(emptyValues[i]);
expect(wrapper.emitted()?.["input"]?.[i]?.[0]).toEqual(expectedValues);
}
});

it("Confirm Select-All checkbox works in various states: select-all, unselect-all, indeterminate/partial-list-selection.", async () => {
const n = 3;
const options = [];

for (let i = 0; i < n; i++) {
options.push({ label: `label_${i}`, value: `value_${i}` });
}

await wrapper.setProps({ options });

const inputs = wrapper.findAll("[type='checkbox']");

/* confirm number of checkboxes requested matches number checkboxes created */
expect(inputs.length).toBe(n + 1);

/* confirm component loads unchecked */
for (let i = 0; i < n + 1; i++) {
expect(inputs.at(i).element.checked).toBeFalsy();
expect((inputs.at(i).element as HTMLInputElement).checked).toBeFalsy();
}

/* 1 - confirm select-all option checked */
await inputs.at(0).setChecked();
expect(inputs.at(0).element.checked).toBeTruthy();
expect((inputs.at(0).element as HTMLInputElement).checked).toBeTruthy();

/* ...confirm corresponding options checked */
const values = options.map((option) => option.value);
expect(wrapper.emitted()["input"][0][0]).toStrictEqual(values);
expect(wrapper.emitted()?.["input"]?.[0]?.[0]).toStrictEqual(values);

/* 2 - confirm select-all option UNchecked */
await inputs.at(0).setChecked(false);
expect(inputs.at(0).element.checked).toBeFalsy();
expect((inputs.at(0).element as HTMLInputElement).checked).toBeFalsy();

/* ...confirm corresponding options UNchecked */
for (let i = 0; i < n; i++) {
expect(inputs.at(i + 1).element.checked).toBeFalsy();
expect((inputs.at(i + 1).element as HTMLInputElement).checked).toBeFalsy();
}

/* 3 - confirm corresponding options indeterminate-state */
await inputs.at(1).setChecked(true);
expect(wrapper.emitted().input[2][0]).toStrictEqual(["value_0"]);
expect(wrapper.emitted()?.input?.[2]?.[0]).toStrictEqual(["value_0"]);

await wrapper.setProps({ value: ["value_0"] });
expect(inputs.at(0).element.indeterminate).toBe(true);
expect((inputs.at(0).element as HTMLInputElement).indeterminate).toBe(true);
});
});
36 changes: 21 additions & 15 deletions client/src/components/Form/Elements/FormCheck.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
<script setup lang="ts">
import { BAlert, BFormCheckbox, BFormCheckboxGroup } from "bootstrap-vue";
import { computed } from "vue";

type Value = string | number | boolean;

interface CheckOption {
label: string;
value: string;
value: Value;
}

export interface FormCheckProps {
value?: string | string[];
options: Array<CheckOption>;
export interface Props {
options: CheckOption[];
value?: Value | Value[];
}

const props = defineProps<FormCheckProps>();
const props = defineProps<Props>();

const emit = defineEmits<{
(e: "input", value: string[] | null): void;
(e: "input", value: Value | Value[] | null): void;
}>();

const currentValue = computed({
get: () => {
const val = props.value ?? [];

return Array.isArray(val) ? val : [val];
},
set: (newValue) => {
Expand All @@ -32,12 +36,13 @@ const currentValue = computed({
});

const hasOptions = computed(() => props.options.length > 0);
const indeterminate = computed(() => ![0, props.options.length].includes(currentValue.value.length));
const selectAll = computed(() => currentValue.value.length === props.options.length);
const indeterminate = computed(() => ![0, props.options.length].includes(currentValue.value.length));

function onSelectAll(selected: boolean): void {
function onSelectAll(selected: boolean) {
if (selected) {
const allValues = props.options.map((option) => option.value);

emit("input", allValues);
} else {
emit("input", null);
Expand All @@ -47,19 +52,20 @@ function onSelectAll(selected: boolean): void {

<template>
<div v-if="hasOptions">
<b-form-checkbox
<BFormCheckbox
v-localize
class="mb-1"
:checked="selectAll"
:indeterminate="indeterminate"
@change="onSelectAll">
Select / Deselect all
</b-form-checkbox>
<b-form-checkbox-group v-model="currentValue" stacked class="pl-3">
<b-form-checkbox v-for="(option, index) in options" :key="index" :value="option.value">
</BFormCheckbox>

<BFormCheckboxGroup v-model="currentValue" stacked class="pl-3">
<BFormCheckbox v-for="(option, index) in options" :key="index" :value="option.value">
{{ option.label }}
</b-form-checkbox>
</b-form-checkbox-group>
</BFormCheckbox>
</BFormCheckboxGroup>
</div>
<b-alert v-else v-localize variant="warning" show> No options available. </b-alert>
<BAlert v-else v-localize variant="warning" show> No options available. </BAlert>
</template>
22 changes: 12 additions & 10 deletions client/src/components/Form/Elements/FormColor.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<script setup lang="ts">
import { BCol, BFormInput, BRow } from "bootstrap-vue";
import { computed } from "vue";

export interface FormColorProps {
value?: string;
export interface Props {
id: string;
value?: string;
}

const props = withDefaults(defineProps<FormColorProps>(), {
const props = withDefaults(defineProps<Props>(), {
value: "",
});

Expand All @@ -25,14 +26,15 @@ const currentValue = computed({
</script>

<template>
<b-row>
<b-col class="form-color-input">
<b-form-input :id="id" v-model="currentValue" class="cursor-pointer" type="color" size="sm" />
</b-col>
<b-col class="pl-0">
<BRow>
<BCol class="form-color-input">
<BFormInput :id="id" v-model="currentValue" class="cursor-pointer" type="color" size="sm" />
</BCol>

<BCol class="pl-0">
<label class="pt-1 cursor-pointer" :for="id">Select a color</label>
</b-col>
</b-row>
</BCol>
</BRow>
</template>

<style scoped>
Expand Down
Loading
Loading