From 70343f586b95c358c17bf08092ae97f8d3ace653 Mon Sep 17 00:00:00 2001 From: Andrew Scott Date: Fri, 27 Oct 2023 16:01:38 -0700 Subject: [PATCH] fix: #332 bindToQueryString is not immediate in vue3 --- src/coalesce-vue/src/model.ts | 40 +++++++------ .../vue3-tests/model.spec.vue3.ts | 57 ++++++++++++++++++- 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/src/coalesce-vue/src/model.ts b/src/coalesce-vue/src/model.ts index 731ef8a54..a08dfc66b 100644 --- a/src/coalesce-vue/src/model.ts +++ b/src/coalesce-vue/src/model.ts @@ -999,6 +999,24 @@ export function bindToQueryString( } ); + const updateObject = (v: any) => { + obj[key] = + // Use the default value if null or undefined + v == null + ? defaultValue + : // Use provided parse function, if provided. + parse + ? parse(v) + : // Use metadata to parse the value if the obj is a DataSource. + obj?.$metadata?.params?.[key] + ? mapToModel(v, obj.$metadata.params[key]) + : obj?.$metadata?.props?.[key] + ? mapToModel(v, obj.$metadata.props[key]) + : // TODO: Add $metadata to DataSourceParameters/FilterParameters/ListParameters, and then support that as well. + // Fallback to the raw value + v; + }; + // When the query changes, grab the new value. vue.$watch( () => vue.$route?.query[queryKey], @@ -1021,24 +1039,12 @@ export function bindToQueryString( } } - obj[key] = - // Use the default value if null or undefined - v == null - ? defaultValue - : // Use provided parse function, if provided. - parse - ? parse(v) - : // Use metadata to parse the value if the obj is a DataSource. - obj?.$metadata?.params?.[key] - ? mapToModel(v, obj.$metadata.params[key]) - : obj?.$metadata?.props?.[key] - ? mapToModel(v, obj.$metadata.props[key]) - : // TODO: Add $metadata to DataSourceParameters/FilterParameters/ListParameters, and then support that as well. - // Fallback to the raw value - v; - }, - { immediate: true } + updateObject(v); + } ); + + // Fix #332: circumvent the `await nextTick` above on the initial call" + updateObject(vue.$route?.query[queryKey]); } export function useBindToQueryString( diff --git a/src/coalesce-vue/vue3-tests/model.spec.vue3.ts b/src/coalesce-vue/vue3-tests/model.spec.vue3.ts index 5b2d1f937..3813c8756 100644 --- a/src/coalesce-vue/vue3-tests/model.spec.vue3.ts +++ b/src/coalesce-vue/vue3-tests/model.spec.vue3.ts @@ -6,6 +6,7 @@ import { RouterView, createWebHistory, RouterLink, + createMemoryHistory, } from "vue-router"; import { bindToQueryString } from "../src"; import { mount } from "@vue/test-utils"; @@ -13,9 +14,6 @@ import { delay } from "../test/test-utils"; describe("bindToQueryString", () => { test("does not put values on new route when changing routes", async () => { - // TO VALIDATE IF THE UNDERLYING PROBLEM IS STILL A PROBLEM: - // Comment the code in bindToQueryString that checks for `isUnmounted` and does a $nextTick. - let changeBoundValue: Function; var router = createRouter({ history: createWebHistory(), // Note: memory history doesn't reproduce the bug. @@ -85,6 +83,59 @@ describe("bindToQueryString", () => { } }); + test("sets query value before returning", async () => { + let boundValueNewValue; + var router = createRouter({ + history: createMemoryHistory(), + routes: [ + { + path: "/", + component: defineComponent({ render: () => h("div") }), + }, + { + path: "/two", + component: defineComponent({ + data() { + return { boundValue: "" }; + }, + created() { + bindToQueryString(this, this, "boundValue"); + boundValueNewValue = this.boundValue; + }, + render: () => h("div"), + }), + }, + ], + }); + + const app = mount( + defineComponent({ + render() { + return h(RouterView); + }, + }), + { + global: { plugins: [router] }, + attachTo: document.body, + } + ); + + try { + // wait for mount + await delay(1); + + // Navigate to another page that uses binding + await router.push("/two?boundValue=foo"); + + // Value from the route should now be in boundValueNewValue. + await delay(1); + expect(boundValueNewValue).toBe("foo"); + } finally { + // Cleanup: reset route for other tests + await router.push("/"); + } + }); + test("handles multiple bound values changing simultaneously", async () => { let setBoundValues: (v: string | null) => void; console.log(window.location.href);