From de822799d70704af0a09aa32ec9b39794642e3ef Mon Sep 17 00:00:00 2001 From: Igor Rogoza <101409489+f4lz@users.noreply.github.com> Date: Thu, 18 Jul 2024 11:13:16 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=9A=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=20`UtilsDate`=20(#200)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### 📝 Описание изменений Добавление компонента `UtilsDate` --------- Co-authored-by: Vasily Kuzin --- docs/.vitepress/config.ts | 1 + docs/components/date.md | 73 ++++++++ docs/index.md | 2 +- package.json | 1 + pnpm-lock.yaml | 27 ++- src/runtime/components/UtilsDate.vue | 89 +++++++++ tests/components/UtilsDate.spec.ts | 258 +++++++++++++++++++++++++++ 7 files changed, 446 insertions(+), 5 deletions(-) create mode 100644 docs/components/date.md create mode 100644 src/runtime/components/UtilsDate.vue create mode 100644 tests/components/UtilsDate.spec.ts diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 030acad..37d2848 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -17,6 +17,7 @@ export default defineConfig({ }, { items: [ + { link: "/components/date", text: "UtilsDate" }, { link: "/components/input", text: "UtilsInput" }, { link: "/components/input-masked", text: "UtilsInputMasked" }, { link: "/components/map-widget", text: "UtilsMapWidget" }, diff --git a/docs/components/date.md b/docs/components/date.md new file mode 100644 index 0000000..7d87174 --- /dev/null +++ b/docs/components/date.md @@ -0,0 +1,73 @@ +# UtilsDate + +Данный компонент выводит поле для выбора даты. + +::: warning + +Для работы данного компонента нужно включить компонент в `build.transpile` в `nuxt.config` + +```ts +export default defineNuxtConfig({ + build: { + transpile: ['@vuepic/vue-datepicker'] + } +}) +``` + +::: + +Компонент может быть использован несколькими способами: + +1. Можно использовать в форме с помощью `formData`. Для этого в компоненте предусмотрен пропс `id` +2. Можно использовать через `v-model`. + +## Пропсы + +- `id` — идентификатор поля ввода. Устанавливает `id` и `name`, которые используются в `formData`. + + **По умолчанию:** `date`. + +- `label` — заголовок для поля. Задает параметр `label`. + +- `placeholder` — плейсхолдер для поля. Задает параметр `placeholder`. + + **По умолчанию:** `Выберите дату`. + +- `disabled` — является ли поле выключенным. Задает параметр `disabled`. + +- `required` — является ли поле обязательным для заполнения. Выставляет атрибут `required`. + + **По умолчанию:** `true`. + +Помимо этого можно использовать любые `props` из документации + +## Стилизация + +Для стилизации компонента подготовлены классы: + +- `utils-date-container`. Его стилизация влияет на контейнер, в котором находится текстовое поле. +- `utils-date-label`. Его стилизация влияет на `label`. +- `utils-date-picker`. Его стилизация влияет на контейнер для `input`. +- `utils-date-input`. Его стилизация влияет на `input`. + +::: info +Также для стилизации можно использовать глобальные классы, которые стилизуют все компоненты формы. + +- `utils-form-container`. Его стилизация влияет на контейнер, в котором находится текстовое поле. +- `utils-form-label`. Его стилизация влияет на `label`. +- `utils-form-input`. Его стилизация влияет на поле ввода. +::: + +## Использование + +```vue + + + +``` diff --git a/docs/index.md b/docs/index.md index bef9548..f29ae72 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,7 +15,7 @@ hero: link: /installation - theme: alt text: Компоненты - link: /components/input + link: /components/date - theme: alt text: Composables link: /composables/use-fetch-auth diff --git a/package.json b/package.json index 5ed4c88..cfdb04b 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "prettier": "@exer7um/prettier-config", "dependencies": { "@nuxt/kit": "~3.12.3", + "@vuepic/vue-datepicker": "~9.0.0", "@vueuse/core": "~10.11.0", "isomorphic-dompurify": "~2.13.0", "maska": "~3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04a9669..94fffe6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@nuxt/kit': specifier: ~3.12.3 version: 3.12.3(magicast@0.3.4)(rollup@4.18.1) + '@vuepic/vue-datepicker': + specifier: ~9.0.0 + version: 9.0.0(vue@3.4.32(typescript@5.5.3)) '@vueuse/core': specifier: ~10.11.0 version: 10.11.0(vue@3.4.32(typescript@5.5.3)) @@ -1691,6 +1694,12 @@ packages: '@vue/test-utils@2.4.6': resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} + '@vuepic/vue-datepicker@9.0.0': + resolution: {integrity: sha512-8ChUOw6F5Sk4iPzu30OOeEYjalSnnOEJKbq9ZehJdkpCEz1s5GFXuxc2A/JsMMdxW9L0Ek9H9MZbJDNTV5RfFA==} + engines: {node: '>=18.12.0'} + peerDependencies: + vue: '>=3.2.0' + '@vueuse/core@10.11.0': resolution: {integrity: sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==} @@ -2221,6 +2230,9 @@ packages: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} + date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + db0@0.1.4: resolution: {integrity: sha512-Ft6eCwONYxlwLjBXSJxw0t0RYtA5gW9mq8JfBXn9TtC0nDPlqePAhpv9v4g9aONBi6JI1OXHTKKkUYGd+BOrCA==} peerDependencies: @@ -4747,8 +4759,8 @@ packages: vue-tsc: optional: true - vite-plugin-inspect@0.8.4: - resolution: {integrity: sha512-G0N3rjfw+AiiwnGw50KlObIHYWfulVwaCBUBLh2xTW9G1eM9ocE5olXkEYUbwyTmX+azM8duubi+9w5awdCz+g==} + vite-plugin-inspect@0.8.5: + resolution: {integrity: sha512-JvTUqsP1JNDw0lMZ5Z/r5cSj81VK2B7884LO1DC3GMBhdcjcsAnJjdWq7bzQL01Xbh+v60d3lju3g+z7eAtNew==} engines: {node: '>=14'} peerDependencies: '@nuxt/kit': '*' @@ -5995,7 +6007,7 @@ snapshots: sirv: 2.0.4 unimport: 3.9.0(rollup@4.18.1) vite: 5.3.4(@types/node@20.14.11)(terser@5.31.3) - vite-plugin-inspect: 0.8.4(@nuxt/kit@3.12.3(magicast@0.3.4)(rollup@4.18.1))(rollup@4.18.1)(vite@5.3.4(@types/node@20.14.11)(terser@5.31.3)) + vite-plugin-inspect: 0.8.5(@nuxt/kit@3.12.3(magicast@0.3.4)(rollup@4.18.1))(rollup@4.18.1)(vite@5.3.4(@types/node@20.14.11)(terser@5.31.3)) vite-plugin-vue-inspector: 5.1.2(vite@5.3.4(@types/node@20.14.11)(terser@5.31.3)) which: 3.0.1 ws: 8.18.0 @@ -6925,6 +6937,11 @@ snapshots: js-beautify: 1.15.1 vue-component-type-helpers: 2.0.26 + '@vuepic/vue-datepicker@9.0.0(vue@3.4.32(typescript@5.5.3))': + dependencies: + date-fns: 3.6.0 + vue: 3.4.32(typescript@5.5.3) + '@vueuse/core@10.11.0(vue@3.4.32(typescript@5.5.3))': dependencies: '@types/web-bluetooth': 0.0.20 @@ -7454,6 +7471,8 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 14.0.0 + date-fns@3.6.0: {} + db0@0.1.4: {} debug@2.6.9: @@ -10258,7 +10277,7 @@ snapshots: optionator: 0.9.4 typescript: 5.5.3 - vite-plugin-inspect@0.8.4(@nuxt/kit@3.12.3(magicast@0.3.4)(rollup@4.18.1))(rollup@4.18.1)(vite@5.3.4(@types/node@20.14.11)(terser@5.31.3)): + vite-plugin-inspect@0.8.5(@nuxt/kit@3.12.3(magicast@0.3.4)(rollup@4.18.1))(rollup@4.18.1)(vite@5.3.4(@types/node@20.14.11)(terser@5.31.3)): dependencies: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.1.0(rollup@4.18.1) diff --git a/src/runtime/components/UtilsDate.vue b/src/runtime/components/UtilsDate.vue new file mode 100644 index 0000000..5e66e23 --- /dev/null +++ b/src/runtime/components/UtilsDate.vue @@ -0,0 +1,89 @@ + + + + diff --git a/tests/components/UtilsDate.spec.ts b/tests/components/UtilsDate.spec.ts new file mode 100644 index 0000000..e92b9ed --- /dev/null +++ b/tests/components/UtilsDate.spec.ts @@ -0,0 +1,258 @@ +import UtilsDate from "@/runtime/components/UtilsDate.vue" +import { mount } from "@vue/test-utils" +import VueDatePicker from "@vuepic/vue-datepicker" +import { describe, expect, test } from "vitest" + +describe("Компонент UtilsDate", () => { + test("Рендер", () => { + /** Компонент */ + const wrapper = mount(UtilsDate) + + // Проверяем рендер компонента + expect(wrapper.element).toBeTruthy() + }) + + test("Стандартные значения `id` и `name`", () => { + const wrapper = mount(UtilsDate) + + /** Текстовое поле */ + const input = wrapper.find("input") + + // Проверка атрибута `id` + expect(input.attributes("id")).toBe("date") + // Проверка атрибута `name` + expect(input.attributes("name")).toBe("date") + }) + + test("Выставление атрибутов `id` и `name` на поле ввода", () => { + /** Компонент */ + const wrapper = mount(UtilsDate, { + props: { + id: "test-field", + }, + }) + + /** Текстовое поле */ + const input = wrapper.find("input") + + // Проверка атрибута `id` + expect(input.attributes("id")).toBe("test-field") + // Проверка атрибута `name` + expect(input.attributes("name")).toBe("test-field") + }) + + test("Отсутствие `label` в разметке без передачи пропса", () => { + /** Компонент */ + const wrapper = mount(UtilsDate) + + /** Элемент `label` */ + const label = wrapper.find("label") + + // Проверка существования `label` + expect(label.exists()).toBeFalsy() + }) + + test("Выставление атрибута `for` на `label`", () => { + /** Компонент */ + const wrapper = mount(UtilsDate, { + props: { + label: "Тестовый label", + }, + }) + + /** Элемент `label` */ + const label = wrapper.find("label") + + // Проверка атрибута `for` + expect(label.attributes("for")).toBe("date") + }) + + test("Выставление текста `label`", () => { + /** Компонент */ + const wrapper = mount(UtilsDate, { + props: { + label: "Тестовый label", + }, + }) + + /** Элемент `label` */ + const label = wrapper.find("label") + + // Проверка атрибута `for` + expect(label.element.textContent).toBe("Тестовый label") + }) + + test("Параметр `disabled` по умолчанию false", () => { + /** Компонент */ + const wrapper = mount(UtilsDate) + + /** Текстовое поле */ + const input = wrapper.find("input") + + // Проверка отсутствия атрибута `disabled`. + expect(input.attributes("disabled")).toBeUndefined() + }) + + test("Выставление параметра `disabled`", () => { + /** Компонент */ + const wrapper = mount(UtilsDate, { + props: { + disabled: true, + }, + }) + + /** Текстовое поле */ + const input = wrapper.find("input") + + // Проверка существования атрибута `disabled`. + // При преобразовании в HTML, true заменяется на пустую строку. + expect(input.attributes("disabled")).toBe("") + }) + + test("Параметр `required` по умолчанию `true`", () => { + /** Компонент */ + const wrapper = mount(UtilsDate) + + /** Текстовое поле */ + const input = wrapper.find("input") + + // Проверка существования атрибута `required`. + // При преобразовании в HTML, true заменяется на пустую строку. + expect(input.attributes("required")).toBe("") + }) + + test("Выставление параметра `required`", () => { + /** Компонент */ + const wrapper = mount(UtilsDate, { + props: { + required: false, + }, + }) + + /** Текстовое поле */ + const input = wrapper.find("input") + + // Проверка отсутствия атрибута `required`. + expect(input.attributes("required")).toBeUndefined() + }) + + test("Работа v-model", async () => { + const parentComponent = mount({ + components: { + UtilsDate, + }, + data() { + return { + date: "", + } + }, + template: ` + + `, + }) + + // Находим внутренний компонент VueDatePicker внутри UtilsDate + const datePickerComponent = parentComponent.findComponent(VueDatePicker) + + // Выставляем значение в компоненте VueDatePicker + await datePickerComponent.vm.$emit("update:model-value", "07/07/2024") + + // Проверяем значение переменной + expect(parentComponent.vm.date).toBe("07/07/2024") + + // Выставляем значение переменной + await parentComponent.setData({ + date: "07/08/2024", + }) + + // Проверяем значение инпута + expect(parentComponent.find("input").element.value).toBe( + "07/08/2024, 00:00" + ) + }) + + test("Работа через `formData`", async () => { + const parentComponent = mount({ + components: { + UtilsDate, + }, + data() { + return { + formData: new FormData(), + } + }, + methods: { + submit(event: Event) { + /** Форма */ + const form = event.target as HTMLFormElement + + /** Объект со всеми данными формы */ + this.formData = new FormData(form) + }, + }, + template: ` +
+ + + `, + }) + + // Находим внутренний компонент VueDatePicker внутри UtilsDate + const datePickerComponent = parentComponent.findComponent(VueDatePicker) + + // Выставляем значение в компоненте VueDatePicker + await datePickerComponent.vm.$emit( + "update:model-value", + "07/07/2024, 00:00" + ) + + /** Форма с полями */ + const form = parentComponent.find("form") + + // Триггерим отправку формы + await form.trigger("submit") + + /** Данные формы */ + const formData = Object.fromEntries(parentComponent.vm.formData.entries()) + + // Проверяем, что правильно выставлены все данные + expect(formData).toStrictEqual({ + date: "07/07/2024, 00:00", + }) + }) + + test("Наличие классов для стилизации", () => { + /** Компонент */ + const wrapper = mount(UtilsDate, { + props: { + label: "Тестовый label", + }, + }) + + /** Классы `wrapper` */ + const wrapperClasses = wrapper.classes() + /** Классы `label` */ + const labelClasses = wrapper.find("label").classes() + /** Классы `input` */ + const inputClasses = wrapper.find("input").classes() + + /** Классы `VueDatePicker` */ + const datePicker = wrapper.findComponent(VueDatePicker).classes() + + // Глобальный класс контейнера + expect(wrapperClasses).toContain("utils-form-container") + // Глобальный класс `label` + expect(labelClasses).toContain("utils-form-label") + // Глобальный класс поля ввода + expect(inputClasses).toContain("utils-form-input") + + // Проверка классов контейнера + expect(wrapperClasses).toContain("utils-date-container") + // Проверка классов у `label` + expect(labelClasses).toContain("utils-date-label") + // Проверка классов у `input` + expect(inputClasses).toContain("utils-date-input") + // Проверка классов у `VueDatePicker` + expect(datePicker).toContain("utils-date-picker") + }) +})