From 1c4813338ab0fe9559d25a782a9485b0aed88496 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Fri, 18 Aug 2023 15:58:29 -0700 Subject: [PATCH] fix(vue3): c-datetime-picker was passing raw strings to validation rules --- .../input/c-datetime-picker.spec.tsx | 65 +++++++++++++++++++ .../components/input/c-datetime-picker.vue | 13 +++- 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/coalesce-vue-vuetify3/src/components/input/c-datetime-picker.spec.tsx diff --git a/src/coalesce-vue-vuetify3/src/components/input/c-datetime-picker.spec.tsx b/src/coalesce-vue-vuetify3/src/components/input/c-datetime-picker.spec.tsx new file mode 100644 index 000000000..2f033b00c --- /dev/null +++ b/src/coalesce-vue-vuetify3/src/components/input/c-datetime-picker.spec.tsx @@ -0,0 +1,65 @@ +import { Grade, Student } from "@test/targets.models"; +import { StudentViewModel } from "@test/targets.viewmodels"; +import { delay, mount, nextTick } from "@test/util"; +import { CDatetimePicker } from ".."; + +describe("CDatetimePicker", () => { + let model: StudentViewModel; + beforeEach(() => { + model = new StudentViewModel({ + name: "bob", + grade: Grade.Freshman, + password: "secretValue", + email: "bob@college.edu", + phone: "123-123-1234", + color: "#ff0000", + notes: "multiline\n\nstring", + }); + }); + + test("caller model - date value", async () => { + const wrapper = mount(() => ( + + )); + + // Assert resting state + expect(wrapper.find("label").text()).toEqual("Date"); + + // Set a value, and look for the value + model.manyParams.args.date = new Date("2023-08-16T01:02:03Z"); + await delay(1); + expect(wrapper.find("input").element.value).contains("2023"); + + // Perform an input on the component, and then look for the new value. + await wrapper.find("input").setValue("1/3/2017"); + await delay(1); + expect(model.manyParams.args.date.getFullYear()).toBe(2017); + }); + + test("validation rules are passed date, not string", async () => { + const rule = vitest.fn( + (v) => !v || v.getFullYear() > 2017 || "Year must be > 2017" + ); + + const wrapper = mount(() => ( + //@ts-ignore useless error about extra properties + + )); + + // Perform an input on the component, and then look at the args that were passed to the rule function: + await wrapper.find("input").setValue("1/3/2017"); + await delay(1); + expect(wrapper.text()).toContain("Year must be > 2017"); + expect(model.birthDate?.getFullYear()).toBe(2017); + expect(rule).toBeCalledTimes(1); + expect(rule).toBeCalledWith(model.birthDate); + + // Do it again, but with a valid input this time. The error should be gone. + await wrapper.find("input").setValue("1/3/2018"); + await delay(1); + expect(wrapper.text()).not.toContain("Year must be > 2017"); + expect(model.birthDate?.getFullYear()).toBe(2018); + expect(rule).toBeCalledTimes(2); + expect(rule).toHaveBeenLastCalledWith(model.birthDate); + }); +}); diff --git a/src/coalesce-vue-vuetify3/src/components/input/c-datetime-picker.vue b/src/coalesce-vue-vuetify3/src/components/input/c-datetime-picker.vue index 9cc6e1c2f..a928f0d58 100644 --- a/src/coalesce-vue-vuetify3/src/components/input/c-datetime-picker.vue +++ b/src/coalesce-vue-vuetify3/src/components/input/c-datetime-picker.vue @@ -11,6 +11,7 @@ " :modelValue="nativeValue" v-bind="inputBindAttrs" + :rules="effectiveRules" :error-messages="error" :readonly="readonly" :disabled="disabled" @@ -23,6 +24,7 @@ v-else class="c-datetime-picker" v-bind="inputBindAttrs" + :rules="effectiveRules" :modelValue="internalTextValue == null ? displayedValue : internalTextValue" :error-messages="error" :readonly="readonly" @@ -208,7 +210,7 @@ export default defineComponent({ return null; }, - internalValue() { + internalValue(): Date | null | undefined { if (this.valueOwner && this.dateMeta) { return (this.valueOwner as any)[this.dateMeta.name]; } @@ -259,6 +261,15 @@ export default defineComponent({ } }, + /** The effective set of validation rules to pass to the text field. Ensures that the real Date value is passed to the rule, rather than the text field's string value. */ + effectiveRules() { + return this.inputBindAttrs.rules?.map( + (ruleFunc: (value: Date | null | undefined) => string | boolean) => + () => + ruleFunc(this.internalValue) + ); + }, + showDate() { return ( this.internalDateKind == "datetime" || this.internalDateKind == "date"