Skip to content

Commit

Permalink
feat: #489 bindToQueryString supports primitive collections from me…
Browse files Browse the repository at this point in the history
…tadata without custom parsing logic
  • Loading branch information
ascott18 committed Dec 19, 2024
1 parent e5cec62 commit ecc95f2
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Added strong types for pass-through Vuetify slots and props to `c-input`, `c-select`, `c-select-many-to-many`, and `c-datetime-picker`.
- Added a `color` prop to `c-datetime-picker`.
- Added experimental client-side support for System.Text.Json's PreserveReferences reference handling option in server responses. This does not require changes to your JSON settings in Program.cs - instead, it is activated by setting `refResponse` on the `DataSourceParameters` for a request (i.e. the `$params` object on a ViewModel or ListViewModel). This option can significantly reduce response sizes in cases where the same object occurs many times in a response.
- `useBindToQueryString`/`bindToQueryString` supports primitive collections from metadata without needing to specify explicit parsing logic

## Fixes

Expand Down
11 changes: 11 additions & 0 deletions src/coalesce-vue/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,17 @@ export function parseValue(
throw parseError(value, meta);

case "collection":
if (
type === "string" &&
meta.itemType.type != "model" &&
meta.itemType.type != "object"
) {
return value
.split(",")
.filter((v: any) => v)
.map((v: any) => parseValue(v, meta.itemType));
}

if (type !== "object" || !Array.isArray(value))
throw parseError(value, meta);

Expand Down
6 changes: 6 additions & 0 deletions src/coalesce-vue/test/model.toModel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from "./model.shared";
import { Course } from "./targets.models";
import { convertToModel } from "../src/model";
import { ComplexModel } from "@test-targets/metadata.g";

const studentProps = $metadata.Student.props;

Expand Down Expand Up @@ -134,6 +135,11 @@ const dtoToModelMappings = <MappingData[]>[
convertToModel({}, $metadata.Course),
],
},
{
meta: ComplexModel.props.intCollection,
dto: "1,2,3",
model: [1, 2, 3],
},
...unparsable(studentProps.courses, "abc", 123, {}, true),

// Model
Expand Down
93 changes: 61 additions & 32 deletions src/coalesce-vue/vue3-tests/model.spec.vue3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,43 @@ import {
import { bindToQueryString, VueInstance } from "../src";
import { mount } from "@vue/test-utils";
import { delay } from "../test/test-utils";
import { reactive } from "vue";
import { Person } from "../../test-targets/models.g";

describe("bindToQueryString", () => {
test("types", async () => {
async function test(func: (v: VueInstance, router: Router) => void) {
var router = createRouter({
history: createWebHistory(),
routes: [
{
path: "/",
component: defineComponent({
created() {
func(this, router);
},
render: () => h("div"),
}),
},
],
});
const app = mount(
defineComponent({
render() {
return h(RouterView);
},
}),
async function runTest(func: (v: VueInstance, router: Router) => void) {
var router = createRouter({
history: createWebHistory(),
routes: [
{
global: { plugins: [router] },
attachTo: document.body,
}
);
path: "/",
component: defineComponent({
created() {
func(this, router);
},
render: () => h("div"),
}),
},
],
});
const app = mount(
defineComponent({
render() {
return h(RouterView);
},
}),
{
global: { plugins: [router] },
attachTo: document.body,
}
);

await delay(10);
}
await delay(10);
}

test("object+key", async () => {
const dateRef = ref<Date>();
await test(async (v, router) => {
await runTest(async (v, router) => {
// Bind to object + key
bindToQueryString(v, dateRef, "value", {
parse(v) {
Expand All @@ -62,8 +64,11 @@ describe("bindToQueryString", () => {
"1970-01-02T10:17:35.667Z"
);
});
});

await test(async (v, router) => {
test("direct bind to ref", async () => {
const dateRef = ref<Date>();
await runTest(async (v, router) => {
// Direct bind to ref
bindToQueryString(v, dateRef, {
queryKey: "foo",
Expand All @@ -80,24 +85,48 @@ describe("bindToQueryString", () => {
"1970-01-02T10:17:35.667Z"
);
});
});

test("Direct bind to ref with queryKey shorthand", async () => {
const stringRef = ref<string>();
await test(async (v, router) => {
await runTest(async (v, router) => {
// Direct bind to ref with queryKey shorthand
bindToQueryString(v, stringRef, "foo");
stringRef.value = "qwerty";
await delay(1);
expect(router.currentRoute.value.query.foo).toBe("qwerty");
});
});

await test(async (v, router) => {
test("bad types", async () => {
await runTest(async (v, router) => {
//@ts-expect-error Missing queryKey in options object.
() => bindToQueryString(v, dateRef, {});
//@ts-expect-error Missing options or queryKey.
() => bindToQueryString(v, dateRef);
});
});

test("bound to primitive collection", async () => {
const dataSource = reactive(
new Person.DataSources.NamesStartingWithAWithCases()
);
await runTest(async (v, router) => {
bindToQueryString(v, dataSource, "allowedStatuses");
dataSource.allowedStatuses = [1, 2];
await delay(1);
expect(router.currentRoute.value.query.allowedStatuses).toBe("1,2");

router.push("/?allowedStatuses=2,3");
await delay(1);
expect(dataSource.allowedStatuses).toStrictEqual([2, 3]);

router.push("/?allowedStatuses=");
await delay(1);
expect(dataSource.allowedStatuses).toStrictEqual([]);
});
});

test("does not put values on new route when changing routes", async () => {
let changeBoundValue: Function;
var router = createRouter({
Expand Down

0 comments on commit ecc95f2

Please sign in to comment.