Skip to content

Commit

Permalink
chore(path): improve code coverage and improve array path access (#199)
Browse files Browse the repository at this point in the history
  • Loading branch information
pikax committed Apr 5, 2020
1 parent 76963e7 commit b9ad3cb
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 13 deletions.
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

0 comments on commit b9ad3cb

Please sign in to comment.