Skip to content

Commit

Permalink
feat: Add support for deeply resolving properties (#56)
Browse files Browse the repository at this point in the history
Closes #55
  • Loading branch information
jtmthf authored and diegohaz committed Jan 17, 2019
1 parent 21a5719 commit 22daf6e
Show file tree
Hide file tree
Showing 17 changed files with 303 additions and 12 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
"eslint-plugin-prettier": "^3.0.0",
"flow-bin": "^0.88.0",
"jest-cli": "^23.6.0",
"lodash": "^4.17.11",
"opn-cli": "^4.0.0",
"polished": "^2.3.1",
"prettier": "^1.15.3",
"react": "^16.6.3",
"react-dom": "^16.6.3",
Expand Down
12 changes: 9 additions & 3 deletions src/ifProp.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
// @flow
/* eslint-disable no-use-before-define */
import prop from "./prop";
import resolveValue from "./resolveValue";
import type { Needle, PropsFn } from ".";

const parseFunction = (props: Object, test: Function): boolean =>
Boolean(test(props));
const parseFunction = (props: Object, test: Function): boolean => {
const result = test(props);
if (typeof result === "function") {
return parseFunction(props, result);
}
return Boolean(result);
};

const parseObject = (props: Object, test: Object): boolean => {
const keys = Object.keys(test);
Expand Down Expand Up @@ -62,7 +68,7 @@ const ifProp = (
}

const value = result ? pass : fail;
return typeof value === "function" ? value(props) : value;
return resolveValue(value, props);
};

export default ifProp;
10 changes: 7 additions & 3 deletions src/palette.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @flow
import resolveValue from "./resolveValue";
import type { PropsWithTheme } from ".";

type Props = PropsWithTheme & {
Expand Down Expand Up @@ -60,13 +61,16 @@ const palette = (
return finalDefaultValue;
}

const tones = toArray(props.theme.palette[key]);
const tones = toArray(resolveValue(props.theme.palette[key], props));

if (tone < 0) {
return tones[clamp(tones.length + tone, 0, tones.length - 1)];
return resolveValue(
tones[clamp(tones.length + tone, 0, tones.length - 1)],
props
);
}

return tones[clamp(tone, 0, tones.length - 1)];
return resolveValue(tones[clamp(tone, 0, tones.length - 1)], props);
};

export default palette;
5 changes: 3 additions & 2 deletions src/prop.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @flow
import resolveValue from "./resolveValue";
import type { PropsFn } from ".";

/**
Expand All @@ -13,7 +14,7 @@ import type { PropsFn } from ".";
*/
const prop = (path: string, defaultValue?: any): PropsFn => (props = {}) => {
if (typeof props[path] !== "undefined") {
return props[path];
return resolveValue(props[path], props);
}

if (path && path.indexOf(".") > 0) {
Expand All @@ -28,7 +29,7 @@ const prop = (path: string, defaultValue?: any): PropsFn => (props = {}) => {
}

if (typeof object !== "undefined") {
return object;
return resolveValue(object, props);
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/resolveValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @flow

const resolveValue = (value: any, props: Object) => {
if (typeof value === "function") {
return resolveValue(value(props), props);
}
return value;
};

export default resolveValue;
7 changes: 5 additions & 2 deletions src/switchProp.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import prop from "./prop";
import resolveValue from "./resolveValue";
import type { Needle, PropsFn } from ".";

/**
Expand Down Expand Up @@ -32,10 +33,12 @@ const switchProp = (
defaultCase: any
): PropsFn => (props = {}) => {
const value =
typeof needle === "function" ? needle(props) : prop(needle)(props);
typeof needle === "function"
? resolveValue(needle, props)
: prop(needle)(props);
const finalCases = typeof cases === "function" ? cases(props) : cases;
if (value in finalCases) {
return finalCases[value];
return resolveValue(finalCases[value], props);
}
return defaultCase;
};
Expand Down
2 changes: 1 addition & 1 deletion src/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ import type { PropsWithTheme } from ".";
* `;
*/
const theme = (path: string, defaultValue?: any) => (props: PropsWithTheme) =>
prop(path, defaultValue)(props.theme);
prop(`theme.${path}`, defaultValue)(props);

export default theme;
7 changes: 6 additions & 1 deletion src/withProp.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import prop from "./prop";
import resolveValue from "./resolveValue";
import type { Needle, PropsFn } from ".";

/**
Expand All @@ -24,7 +25,11 @@ const withProp = (needle: Needle | Needle[], fn: Function): PropsFn => (
return fn(...needles);
}
if (typeof needle === "function") {
return fn(needle(props));
const value = resolveValue(needle, props);
if (Array.isArray(value)) {
return withProp(value, fn)(props);
}
return fn(value);
}
return fn(prop(needle)(props));
};
Expand Down
53 changes: 53 additions & 0 deletions test/complex.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { getLuminance, tint } from "polished";
import { range } from "lodash";
import { palette as p, theme as t, withProp } from "../src";

export const contrastText = bgNeedle =>
withProp(bgNeedle, bg =>
getLuminance(bg) > 0.179 ? p("black") : p("white")
);

export const contrastPalette = (key, tone) => contrastText(p(key, tone));

export const tintPalette = (amount, key, tone) =>
withProp(p(key, tone), color => tint(amount, color));

export const tonePalette = (key, tone) =>
withProp(t("colorInterval"), colorInterval =>
range(5)
.map(i => i * colorInterval)
.map(i => tintPalette(i, key, tone))
);

export const tonePaletteText = key =>
range(5).map(i => contrastPalette(key, i));

export const tonePaletteWithText = (name, key, tone) => ({
[name]: tonePalette(key, tone),
[`${name}Text`]: tonePaletteText(name)
});

const theme = {
palette: {
white: "#fff",
black: "#000",

blue: "#007bff",

...tonePaletteWithText("primary", "blue")
},
colorInterval: 0.08
};

test("complex", () => {
expect(p("primary")({ theme })).toBe("#007bff");
expect(p("primaryText")({ theme })).toBe("#000");
expect(p("primary", 1)({ theme })).toBe("#1485ff");
expect(p("primaryText", 1)({ theme })).toBe("#000");
expect(p("primary", 2)({ theme })).toBe("#2890ff");
expect(p("primaryText", 2)({ theme })).toBe("#000");
expect(p("primary", 3)({ theme })).toBe("#3d9aff");
expect(p("primaryText", 3)({ theme })).toBe("#000");
expect(p("primary", 4)({ theme })).toBe("#51a5ff");
expect(p("primaryText", 4)({ theme })).toBe("#000");
});
23 changes: 23 additions & 0 deletions test/ifNotProp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,26 @@ test("function values", () => {
ifNotProp("foo", props => props.bar, props => props.foo)({ foo: "foo" })
).toBe("foo");
});

test("deep function argument", () => {
expect(ifNotProp(() => props => props.foo, "no", "yes")()).toBe("no");
expect(ifNotProp(() => props => props.foo, "no", "yes")({ foo: false })).toBe(
"no"
);
expect(ifNotProp(() => props => props.foo, "no", "yes")({ foo: true })).toBe(
"yes"
);
});

test("deep function values", () => {
expect(
ifNotProp("foo", () => props => props.bar, () => props => props.foo)({
bar: "bar"
})
).toBe("bar");
expect(
ifNotProp("foo", () => props => props.bar, () => props => props.foo)({
foo: "foo"
})
).toBe("foo");
});
35 changes: 35 additions & 0 deletions test/ifProp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,38 @@ test("function values", () => {
ifProp("foo", props => props.foo, props => props.bar)({ foo: "foo" })
).toBe("foo");
});

test("deep function argument", () => {
expect(ifProp(() => props => props.foo, "yes", "no")()).toBe("no");
expect(ifProp(() => props => props.foo, "yes", "no")({ foo: false })).toBe(
"no"
);
expect(ifProp(() => props => props.foo, "yes", "no")({ foo: true })).toBe(
"yes"
);
});

test("deep function values", () => {
expect(
ifProp("foo", () => props => props.foo, () => props => props.bar)({
bar: "bar"
})
).toBe("bar");
expect(
ifProp("foo", () => props => props.foo, () => props => props.bar)({
foo: "foo"
})
).toBe("foo");
});

test("deep function array argument", () => {
expect(
ifProp([() => props => props.foo], "yes", "no")({ bar: false, foo: true })
).toBe("yes");
expect(
ifProp(["bar", () => props => props.foo], "yes", () => "no")({
bar: false,
foo: true
})
).toBe("no");
});
57 changes: 57 additions & 0 deletions test/palette.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ const theme = {
}
};

const deepTheme = {
palette: {
primary: [() => "primary0", () => "primary1", () => "primary2"],
secondary: () => "secondary0"
}
};

test("only tone", () => {
expect(palette()({ theme: {} })).toBeUndefined();
expect(palette()({ theme })).toBeUndefined();
Expand Down Expand Up @@ -44,3 +51,53 @@ test("palette and tone", () => {
expect(palette("other", 1)({ theme, palette: "primary" })).toBeUndefined();
expect(palette("other", 1, "foo")({ theme, palette: "primary" })).toBe("foo");
});

test("deep only tone", () => {
expect(palette()({ theme: deepTheme })).toBeUndefined();
expect(palette(0)({ theme: deepTheme })).toBeUndefined();
expect(palette()({ theme: deepTheme, palette: "primary" })).toBe("primary0");
expect(palette()({ theme: deepTheme, palette: "primary", tone: 2 })).toBe(
"primary2"
);
expect(palette(0)({ theme: deepTheme, palette: "primary" })).toBe("primary0");
expect(palette(1)({ theme: deepTheme, palette: "primary" })).toBe("primary1");
expect(palette(-1)({ theme: deepTheme, palette: "primary" })).toBe(
"primary2"
);
expect(palette(-5)({ theme: deepTheme, palette: "primary" })).toBe(
"primary0"
);
expect(palette(5)({ theme: deepTheme, palette: "primary" })).toBe("primary2");
expect(palette(0)({ theme: deepTheme, palette: "secondary" })).toBe(
"secondary0"
);
expect(palette(1)({ theme: deepTheme, palette: "secondary" })).toBe(
"secondary0"
);
expect(palette(0)({ theme: deepTheme, palette: "other" })).toBeUndefined();
expect(palette(1, "foo")({ theme: deepTheme })).toBe("foo");
expect(palette(1, "foo")({ theme: deepTheme, palette: "other" })).toBe("foo");
});

test("deep palette and tone", () => {
expect(palette("primary")({ theme: deepTheme })).toBe("primary0");
expect(palette("primary")({ theme: deepTheme, tone: 2 })).toBe("primary2");
expect(palette("primary", 0)({ theme: deepTheme })).toBe("primary0");
expect(palette("primary", 5)({ theme: deepTheme })).toBe("primary2");
expect(palette("primary", -1)({ theme: deepTheme })).toBe("primary2");
expect(palette("primary", -5)({ theme: deepTheme })).toBe("primary0");
expect(palette("secondary", 0)({ theme: deepTheme })).toBe("secondary0");
expect(palette("secondary", 1)({ theme: deepTheme })).toBe("secondary0");
expect(palette("secondary", 1, "foo")({ theme: deepTheme })).toBe(
"secondary0"
);
expect(palette("other", 1)({ theme: deepTheme })).toBeUndefined();
expect(palette("other", "foo")({ theme: deepTheme })).toBe("foo");
expect(palette("other", 1, "foo")({ theme: deepTheme })).toBe("foo");
expect(
palette("other", 1)({ theme: deepTheme, palette: "primary" })
).toBeUndefined();
expect(
palette("other", 1, "foo")({ theme: deepTheme, palette: "primary" })
).toBe("foo");
});
10 changes: 10 additions & 0 deletions test/prop.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@ test("string argument", () => {
expect(prop("color")()).toBeUndefined();
expect(prop("color")({})).toBeUndefined();
expect(prop("color")({ color: "red" })).toBe("red");
expect(prop("color")({ color: () => "red" })).toBe("red");
expect(prop("color")({ color: props => props.bg, bg: "red" })).toBe("red");
});

test("deep string argument", () => {
expect(prop("color.primary")()).toBeUndefined();
expect(prop("color.primary")({})).toBeUndefined();
expect(prop("color.primary")({ color: {} })).toBeUndefined();
expect(prop("color.primary")({ color: { primary: "red" } })).toBe("red");
expect(prop("color.primary")({ color: { primary: () => "red" } })).toBe(
"red"
);
expect(
prop("color.primary")({
color: { primary: prop("color.secondary"), secondary: "blue" }
})
).toBe("blue");
});

test("defaultValue", () => {
Expand Down
13 changes: 13 additions & 0 deletions test/switchProp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ test("string argument", () => {
expect(
switchProp("type", { red: "red", blue: "blue" })({ type: "blue" })
).toBe("blue");
expect(
switchProp("type", { red: "red", blue: "blue" })({ type: () => "red" })
).toBe("red");
expect(
switchProp("type", { red: () => "red", blue: () => "blue" })({
type: "red"
})
).toBe("red");
});

test("deep string argument", () => {
Expand Down Expand Up @@ -35,6 +43,11 @@ test("function argument", () => {
type: "blue"
})
).toBe("blue");
expect(
switchProp(() => props => props.type, { red: "red", blue: "blue" })({
type: "red"
})
).toBe("red");
});

test("default case", () => {
Expand Down
Loading

0 comments on commit 22daf6e

Please sign in to comment.