Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## Changes

- [path](https://pikax.me/vue-composable/composable/format/path) - Improve [array path access](https://pikax.me/vue-composable/composable/format/path.html#access) and add dev warnings
- [i18n](https://pikax.me/vue-composable/composable/i18n/i18n) - Allow to have factory based locale messages
- [i18n](https://pikax.me/vue-composable/composable/i18n/i18n) - Added console warnings when removing locales
- [i18n](https://pikax.me/vue-composable/composable/i18n/i18n) - Improve overriding locales
Expand Down
33 changes: 33 additions & 0 deletions docs/composable/format/path.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,39 @@ const name = usePath<string>({ user: { name: "test" } }, "user.name");
| ----- | -------- | ------------------------------------------------- |
| name | `Ref<T>` | Readonly `ref` with the object value for the path |

## Access

```js
const o = {
a: {
a: 1,
b: [
2,
{
c: {
["a-b-c-d"]: 3
}
}
]
}
};

usePath(o, "a[a]"); // result: 1 | equivalent: a.a
usePath(o, "[a]['a']"); // result: 1 | equivalent: a.a
usePath(o, '["a"][`b`][0]'); // result: 2 | equivalent: a.b["0"]
usePath(o, "a.b[1].c[a-b-c-d]"); // result: 3 | equivalent: a.b[1].c["a-b-c-d"]
```

## Limitations

The access in `[]` is limited to this regex expression:

```regex
/\[[`'"]?([^`'"\]]*)[`'"]?\]/g
```

If you want to improve this, please raise an [issue](https://github.com/pikax/vue-composable/issues/new) or create a [PR](https://github.com/pikax/vue-composable/pulls)

## Example

<path-example/>
Expand Down
103 changes: 103 additions & 0 deletions packages/core/__tests__/format/path.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { usePath } from "../../src";
import { ref } from "@vue/composition-api";

describe("path", () => {
it("should return the object value", () => {
Expand Down Expand Up @@ -35,4 +36,106 @@ describe("path", () => {
expect(usePath(o, "array[1]").value).toBe(2);
expect(usePath(o, "deep.x[1].a.b").value).toBe(1);
});

describe("not found", () => {
const notFoundResolverMock = jest.fn().mockImplementation(() => "test");

const warnSpy = jest.spyOn(console, "warn");

beforeEach(() => {
notFoundResolverMock.mockClear();
warnSpy.mockClear();
});

test("source `undefined`", () => {
expect(
usePath(ref(undefined), "yey", undefined, notFoundResolverMock).value
).toBe("test");
expect(notFoundResolverMock).toHaveBeenLastCalledWith(
"yey",
undefined,
"yey",
undefined
);
});

test("no path", () => {
const o = {
a: 1
};
expect(usePath(o, "", undefined, notFoundResolverMock).value).toBe(o);
expect(notFoundResolverMock).not.toBeCalled();
});

test("first path not found", () => {
const o = {
a: 1
};
expect(usePath(o, "b", undefined, notFoundResolverMock).value).toBe(
"test"
);
expect(notFoundResolverMock).toBeCalled();
expect(warnSpy).toBeCalledWith(`Path "b" doesn't exist on:`, o);
});

test("deep path not found", () => {
const o = {
a: {
c: "hello"
}
};
expect(usePath(o, "a.c.a", undefined, notFoundResolverMock).value).toBe(
"test"
);
expect(notFoundResolverMock).toBeCalled();
expect(warnSpy).toBeCalledWith(`Path "a.c.a" doesn't exist on:`, o);
});

test("if access with []", () => {
const o = {};
expect(usePath(o, "[]", undefined, notFoundResolverMock).value).toBe(
"test"
);
expect(warnSpy).toBeCalledWith(`Path "[]" doesn't exist on:`, o);
});

test("access with consecutive []", () => {
const o = {
a: {
a: 1,
b: [
2,
{
c: {
["a-b-c-d"]: 3
}
}
]
}
};

expect(usePath(o, "a[a]").value).toBe(o.a.a);
expect(usePath(o, "[a]['a']").value).toBe(o.a.a);
expect(usePath(o, '["a"][`b`][0]').value).toBe(o.a.b[0]);
expect(usePath(o, "a.b[1].c[a-b-c-d]").value).toBe(
(o.a.b[1] as any).c["a-b-c-d"]
);
});

test("invalid path parsing", () => {
expect(usePath({}, "a[a]o[a]").value).toBeUndefined();
expect(warnSpy).toHaveBeenNthCalledWith(
1,
`[usePath] invalid path "a[a]o[a]"`
);
});

test("invalid array accessor", () => {
expect(usePath({}, "aa]").value).toBeUndefined();
expect(warnSpy).toHaveBeenNthCalledWith(
1,
`[usePath] invalid path provided "aa]"`
);
});
});
});
88 changes: 75 additions & 13 deletions packages/core/src/format/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,98 @@ export function usePath<T = any>(
let c = s;
for (let i = 0; i < fragments.length; i++) {
let fragmentPath = fragments[i];
let index: any = -1;

if (fragmentPath[fragmentPath.length - 1] === "]") {
const m = fragmentPath.match(/\[(\d+)\]$/);
if (m && m[1]) {
index = +m[1];
const r = /\[[`'"]?([^`'"\]]*)[`'"]?\]/g;
let path = fragmentPath;
let m = r.exec(path);

fragmentPath = fragmentPath.slice(0, -m[0].length);
if (m) {
let lastLen = m[0].length;
let lastIndex = m.index - lastLen;
let mi = 1;

do {
if (lastIndex + lastLen !== m.index) {
// istanbul ignore else
if (__DEV__) {
console.warn(`[usePath] invalid path "${fragments[i]}"`);
}
}
lastIndex = m.index;
lastLen = m[0].length;

fragmentPath = fragmentPath.slice(0, -m[0].length);
fragments.splice(i + mi, 0, m[1]);

++mi;
} while ((m = r.exec(path)));

// if the fragmentPath is empty, eg: [1][1]
// we should continue until the next path
if (!fragmentPath && path[0] === "[" && path.length > 2) {
continue;
}
} else {
fragmentPath = "";
console.warn(`[usePath] invalid path provided "${path}"`);
}
}

if (isObject(c)) {
c = c[fragmentPath];

// array like: when using ref with and array, it becomes arraylike object
if (index >= 0) {
c = (c as any)[index];
if (!fragmentPath) {
// istanbul ignore else
if (__DEV__) {
console.warn(
`Path "${fragments
.slice(0, i + 1)
.join(separator)}" doesn't exist on:`,
source
);
}
return notFoundReturn(
fragments.slice(0, i + 1).join(separator),
c,
p,
s
);
}

c = c[fragmentPath];
} else {
// istanbul ignore else
if (__DEV__) {
console.warn(
`Path "${fragments.slice(0, i).join(separator)}" doesn't exist on:`,
`Path "${fragments
.slice(0, i + 1)
.join(separator)}" doesn't exist on:`,
source
);
}
return notFoundReturn(fragments.slice(0, i).join(separator), c, p, s);
return notFoundReturn(
fragments.slice(0, i + 1).join(separator),
c,
p,
s
);
}

if (!c) {
return notFoundReturn(fragments.slice(0, i).join(separator), c, p, s);
// istanbul ignore else
if (__DEV__) {
console.warn(
`Path "${fragments
.slice(0, i + 1)
.join(separator)}" doesn't exist on:`,
source
);
}
return notFoundReturn(
fragments.slice(0, i + 1).join(separator),
c,
p,
s
);
}
}

Expand Down