diff --git a/.eslintrc.json b/.eslintrc.json index 82c504e..1e92946 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,26 +1,35 @@ { "extends": [ "eslint:recommended", - "plugin:jsdoc/recommended", - "plugin:node/recommended", - "plugin:import/recommended", - "plugin:compat/recommended", "plugin:react/recommended", - "plugin:prettier/recommended" + "plugin:react-hooks/recommended" ], - "plugins": ["jsdoc", "react-hooks"], "parserOptions": { - "ecmaVersion": "latest" + "ecmaVersion": "latest", + "sourceType": "script" }, "settings": { "react": { "version": "detect" } }, + "env": { + "browser": true, + "node": true, + "es2022": true + }, + "overrides": [ + { + "files": ["**/*.mjs"], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + } + } + ], "rules": { "arrow-body-style": ["error", "always"], "curly": ["error", "all"], - "jsdoc/require-jsdoc": "off", "func-style": ["error", "declaration"] } } diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 0ea4d72..be15b23 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -6,8 +6,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - os: [ubuntu-latest, macos-latest] - node: ["14", "16", "18", "19"] + os: [ubuntu-latest] + node: ["18", "19"] steps: - uses: actions/checkout@v3 - name: Setup Node.js v${{ matrix.node }} diff --git a/.prettierignore b/.prettierignore index 87925bc..ec6d3cd 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1 @@ package.json -/snapshots diff --git a/Global.js b/Global.js index b6081c6..ae575ed 100644 --- a/Global.js +++ b/Global.js @@ -3,13 +3,12 @@ const { Global: EmotionGlobal } = require("@emotion/react"); const PropTypes = require("prop-types"); const React = require("react"); -const transformStyles = require("./private/transformStyles.js"); +const css = require("./private/css.js"); const useMystical = require("./useMystical.js"); -function Global({ styles: initialStyles }) { +function Global({ styles }) { const context = useMystical(); - const styles = transformStyles(initialStyles)(context); - return React.createElement(EmotionGlobal, { styles }); + return React.createElement(EmotionGlobal, { styles: css(styles)(context) }); } Global.propTypes = { diff --git a/MysticalProvider.js b/MysticalProvider.js index 9a36be5..cfba370 100644 --- a/MysticalProvider.js +++ b/MysticalProvider.js @@ -5,26 +5,39 @@ const PropTypes = require("prop-types"); const React = require("react"); const Global = require("./Global.js"); const customProperties = require("./private/customProperties.js"); +const useMystical = require("./useMystical.js"); -function MysticalProvider({ theme = {}, children }) { - return React.createElement( - ThemeProvider, - { theme: { theme } }, - React.createElement(Global, { - styles: { - ":root": customProperties(theme.colors, "colors"), +function MysticalGlobalStyles() { + const { theme } = useMystical(); + + return React.createElement(Global, { + styles: [ + { ["*, *::before,*::after"]: { boxSizing: "border-box", }, - ...theme.global, }, - }), + theme.colors && { ":root": customProperties(theme.colors, "colors") }, + theme.global, + ], + }); +} + +function MysticalProvider({ theme, options = {}, children }) { + return React.createElement( + ThemeProvider, + { theme: { theme, options } }, + React.createElement(MysticalGlobalStyles), children ); } MysticalProvider.propTypes = { - theme: PropTypes.object.isRequired, + theme: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired, + options: PropTypes.shape({ + darkModeOff: PropTypes.bool, + darkModeForcedBoundary: PropTypes.bool, + }), children: PropTypes.node.isRequired, }; diff --git a/darkColorMode.js b/darkColorMode.js index 4699efb..97fd82f 100644 --- a/darkColorMode.js +++ b/darkColorMode.js @@ -1,5 +1,5 @@ "use strict"; -const darkColorMode = "@media (prefers-color-scheme: dark)"; +const darkColorMode = "__MYSTICAL_INTERNAL_DARK_MODE__"; module.exports = darkColorMode; diff --git a/package.json b/package.json index 9a4a8c8..d280fdb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "mystical", "description": "CSS prop constraint based styling", - "version": "14.0.0", + "version": "15.0.0-beta.2", "author": "David Burles", "license": "MIT", "repository": "github:dburles/mystical", @@ -15,22 +15,20 @@ "react" ], "engines": { - "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" + "node": "^18.0.0 || >= 19.0.0" }, - "browserslist": "Node 14.17 - 15 and Node < 15, Node >= 16, > 0.5%, not OperaMini all, not IE > 0, not dead", + "type": "commonjs", "sideEffects": false, "files": [ "darkColorMode.js", "defaultColorMode.js", "Global.js", "index.d.ts", - "InitializeColorMode.js", "jsx-dev-runtime.js", "jsx-runtime.js", "jsx.js", "MysticalProvider.js", "private", - "useColorMode.js", "useModifiers.js", "useMystical.js", "useTheme.js" @@ -40,13 +38,11 @@ "./defaultColorMode.js": "./defaultColorMode.js", "./Global.js": "./Global.js", "./index.d.ts": "./index.d.ts", - "./InitializeColorMode.js": "./InitializeColorMode.js", "./jsx-dev-runtime": "./jsx-dev-runtime.js", "./jsx-runtime": "./jsx-runtime.js", "./jsx.js": "./jsx.js", "./MysticalProvider.js": "./MysticalProvider.js", "./package.json": "./package.json", - "./useColorMode.js": "./useColorMode.js", "./useModifiers.js": "./useModifiers.js", "./useMystical.js": "./useMystical.js", "./useTheme.js": "./useTheme.js" @@ -56,25 +52,18 @@ "test": "npm run test:lint && npm run test:prettier && npm run test:unit", "test:lint": "eslint .", "test:prettier": "prettier -c .", - "test:unit": "node test/run.mjs", + "test:unit": "node --test", "prepublishOnly": "npm test" }, "devDependencies": { "eslint": "^8.8.0", "eslint-config-prettier": "^8.3.0", - "eslint-plugin-compat": "^4.0.2", - "eslint-plugin-import": "^2.23.3", - "eslint-plugin-jsdoc": "^39.3.6", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.23.2", "eslint-plugin-react-hooks": "^4.2.0", "prettier": "^2.3.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-test-renderer": "^18.2.0", - "snapshot-assertion": "^5.0.0", - "test-director": "^10.0.0" + "react-test-renderer": "^18.2.0" }, "dependencies": { "@emotion/react": "^11.4.0", diff --git a/private/createJsxFn.js b/private/createJsxFn.js index be1c9c9..782a688 100644 --- a/private/createJsxFn.js +++ b/private/createJsxFn.js @@ -1,14 +1,14 @@ "use strict"; -const transformStyles = require("./transformStyles.js"); +const css = require("./css.js"); function createJsxFn(jsxFn) { return (type, props, ...rest) => { - const { css, ...restProps } = props || {}; + const { css: styles, ...restProps } = props || {}; return jsxFn( type, - css ? { css: transformStyles(css), ...restProps } : props, + styles ? { css: css(styles), ...restProps } : props, ...rest ); }; diff --git a/private/css.js b/private/css.js new file mode 100644 index 0000000..d9e6ccb --- /dev/null +++ b/private/css.js @@ -0,0 +1,82 @@ +"use strict"; + +const darkColorMode = require("../darkColorMode.js"); +const facepaint = require("./facepaint.js"); +const forceDarkModeAttribute = require("./forceDarkModeAttribute.js"); +const isObject = require("./isObject.js"); +const merge = require("./merge.js"); +const negativeTransform = require("./negativeTransform.js"); +const shorthandProperties = require("./shorthandProperties.js"); +const themeTokens = require("./themeTokens.js"); +const transformColors = require("./transformColors.js"); + +function transformStyle(property, value, theme = {}) { + const themeKey = themeTokens[property]; + + if (shorthandProperties[property]) { + return shorthandProperties[property](theme, String(value)); + } else if (themeKey) { + let currentThemeProperties = theme[themeKey]; + if (themeKey === "colors") { + return transformColors(theme.colors, property, value); + } + const transformNegatives = negativeTransform(property); + return { + [property]: transformNegatives(currentThemeProperties, value, value), + }; + } + return { [property]: value }; +} + +function css(rootStyles) { + let mergedStyles = Array.isArray(rootStyles) + ? merge(...rootStyles) + : rootStyles; + + return (context) => { + let transformedStyles = {}; + + function transformStyles(styles) { + for (const property in styles) { + const value = styles[property]; + if (isObject(value)) { + if (property === darkColorMode) { + const transformed = css(value)(context); + if (!context.options?.darkModeOff) { + transformedStyles["@media (prefers-color-scheme: dark)"] = + transformed; + } + if (context.options?.darkModeForcedBoundary) { + transformedStyles[`[${forceDarkModeAttribute}] &`] = transformed; + } + } else { + transformedStyles[property] = css(value)(context); + } + } else { + transformedStyles = { + ...transformedStyles, + ...transformStyle(property, value, context.theme), + }; + } + } + } + + if (Array.isArray(context.theme?.breakpoints)) { + const mq = facepaint( + context.theme.breakpoints.map((bp) => { + return `@media (min-width: ${bp})`; + }) + ); + + for (const styles of mq(mergedStyles)) { + transformStyles(styles); + } + } else { + transformStyles(mergedStyles); + } + + return transformedStyles; + }; +} + +module.exports = css; diff --git a/private/css.test.mjs b/private/css.test.mjs new file mode 100644 index 0000000..a8cf505 --- /dev/null +++ b/private/css.test.mjs @@ -0,0 +1,92 @@ +import test from "node:test"; +import css from "./css.js"; +import theme from "../test-utils/theme.mjs"; +import assert from "node:assert/strict"; +import darkColorMode from "../darkColorMode.js"; +import forceDarkModeAttribute from "./forceDarkModeAttribute.js"; + +test("css", async (t) => { + await t.test("colors", () => { + const styles = css({ + borderColor: "orange.500", + color: "orange.500", + })({ theme }); + + assert.deepEqual(styles, { + borderColor: "var(--colors-orange-500)", + color: "var(--colors-orange-500)", + }); + }); + + await t.test("media query", () => { + const styles = css({ + color: ["orange.500", "blue.500", "red.500", "pink.500"], + })({ theme }); + + assert.deepEqual(styles, { + color: "var(--colors-orange-500)", + "@media (min-width: 640px)": { color: "var(--colors-blue-500)" }, + "@media (min-width: 768px)": { color: "var(--colors-red-500)" }, + "@media (min-width: 1024px)": { color: "var(--colors-pink-500)" }, + }); + }); + + await t.test("no breakpoints defined", () => { + // eslint-disable-next-line no-unused-vars + const { breakpoints, ...themeWithoutBreakpoints } = theme; + // Becomes a fallback: https://emotion.sh/docs/object-styles#fallbacks + const styles = css({ + color: ["orange.500", "blue.500", "red.500", "pink.500"], + })({ theme: themeWithoutBreakpoints }); + + assert.deepEqual(styles, { + color: ["orange.500", "blue.500", "red.500", "pink.500"], + }); + }); + + await t.test("dark mode", async (tt) => { + await tt.test("transform", () => { + const styles = css({ + [darkColorMode]: { + color: "red", + }, + })({}); + + assert.deepEqual(styles, { + "@media (prefers-color-scheme: dark)": { + color: "red", + }, + }); + }); + + await tt.test("transform with darkModeOff = true", () => { + const styles = css({ + [darkColorMode]: { + color: "red", + }, + })({ options: { darkModeOff: true } }); + + assert.deepEqual(styles, {}); + }); + + await tt.test( + "transform with options.darkModeForcedBoundary = true", + () => { + const styles = css({ + [darkColorMode]: { + color: "red", + }, + })({ options: { darkModeForcedBoundary: true } }); + + assert.deepEqual(styles, { + "@media (prefers-color-scheme: dark)": { + color: "red", + }, + [`[${forceDarkModeAttribute}] &`]: { + color: "red", + }, + }); + } + ); + }); +}); diff --git a/private/customProperties.test.mjs b/private/customProperties.test.mjs new file mode 100644 index 0000000..a3672b0 --- /dev/null +++ b/private/customProperties.test.mjs @@ -0,0 +1,53 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import customProperties from "./customProperties.js"; + +test("customProperties", async (t) => { + await t.test("self referencing", () => { + const colors = { + brand: { + primary: "emerald.500", + }, + emerald: { + 50: "#ecfdf5", + 100: "#d1fae5", + 200: "#a7f3d0", + 300: "#6ee7b7", + 400: "#34d399", + 500: "#10b981", + 600: "#059669", + 700: "#047857", + 800: "#065f46", + 900: "#064e3b", + }, + }; + + const result = customProperties(colors, "colors"); + assert.equal(result["--colors-brand-primary"], colors.emerald["500"]); + }); + + await t.test("self referencing (array)", () => { + const colors = { + brand: { + primary: ["emerald.500", "emerald.100"], + }, + emerald: { + 50: "#ecfdf5", + 100: "#d1fae5", + 200: "#a7f3d0", + 300: "#6ee7b7", + 400: "#34d399", + 500: "#10b981", + 600: "#059669", + 700: "#047857", + 800: "#065f46", + 900: "#064e3b", + }, + }; + + const result = customProperties(colors, "colors"); + + assert.equal(result["--colors-brand-primary-0"], colors.emerald["500"]); + assert.equal(result["--colors-brand-primary-1"], colors.emerald["100"]); + }); +}); diff --git a/private/forceDarkModeAttribute.js b/private/forceDarkModeAttribute.js new file mode 100644 index 0000000..80fe3e5 --- /dev/null +++ b/private/forceDarkModeAttribute.js @@ -0,0 +1,5 @@ +"use strict"; + +const forceDarkModeAttribute = 'data-mystical-color-mode="dark"'; + +module.exports = forceDarkModeAttribute; diff --git a/private/mergeModifiers.test.mjs b/private/mergeModifiers.test.mjs new file mode 100644 index 0000000..7854a9c --- /dev/null +++ b/private/mergeModifiers.test.mjs @@ -0,0 +1,74 @@ +import assert from "node:assert/strict"; +import mergeModifiers from "./mergeModifiers.js"; +import test from "node:test"; + +const modifiers = { + default: { + fontFamily: "heading", + }, + size: { + small: { + fontSize: 3, + }, + large: { + fontSize: 5, + }, + }, +}; + +const modifiers2 = { + default: { + title: { fontFamily: "heading" }, + subtitle: { fontFamily: "body" }, + }, + size: { + small: { + title: { fontSize: 3 }, + subtitle: { fontSize: 0 }, + }, + large: { + title: { fontSize: 5 }, + subtitle: { fontSize: 2 }, + }, + }, +}; + +test("mergeModifiers", async (t) => { + await t.test("basic", async () => { + const modifierStyles = mergeModifiers({ size: "small" }, modifiers); + + assert.equal(Object.keys(modifierStyles).length, 2); + assert.equal(modifierStyles.fontFamily, "heading"); + assert.equal(modifierStyles.fontSize, 3); + }); + + await t.test("basic with customModifiers", async () => { + const modifierStyles = mergeModifiers({ size: "small" }, modifiers, { + fontSize: 4, + }); + + assert.equal(Object.keys(modifierStyles).length, 2); + assert.equal(modifierStyles.fontFamily, "heading"); + assert.equal(modifierStyles.fontSize, 4); + }); + + await t.test("multiple elements", async () => { + const modifierStyles = mergeModifiers({ size: "small" }, modifiers2); + + assert.equal(Object.keys(modifierStyles).length, 2); + assert.equal(modifierStyles.title.fontFamily, "heading"); + assert.equal(modifierStyles.subtitle.fontFamily, "body"); + assert.equal(modifierStyles.title.fontSize, 3); + assert.equal(modifierStyles.subtitle.fontSize, 0); + }); + + await t.test("multiple elements with customModifiers", async () => { + const modifierStyles = mergeModifiers({ size: "small" }, modifiers2, { + title: { + fontSize: 4, + }, + }); + + assert.equal(modifierStyles.title.fontSize, 4); + }); +}); diff --git a/private/transformStyles.js b/private/transformStyles.js deleted file mode 100644 index 5c0b51e..0000000 --- a/private/transformStyles.js +++ /dev/null @@ -1,57 +0,0 @@ -"use strict"; - -const facepaint = require("./facepaint.js"); -const isObject = require("./isObject.js"); -const merge = require("./merge.js"); -const negativeTransform = require("./negativeTransform.js"); -const shorthandProperties = require("./shorthandProperties.js"); -const themeTokens = require("./themeTokens.js"); -const transformColors = require("./transformColors.js"); - -function transformStyle(key, value, { theme }) { - const themeKey = themeTokens[key]; - - if (shorthandProperties[key]) { - return shorthandProperties[key](theme, String(value)); - } else if (themeKey) { - let currentThemeProperties = theme[themeKey]; - if (themeKey === "colors") { - return transformColors(theme.colors, key, value); - } - const transformNegatives = negativeTransform(key); - return { [key]: transformNegatives(currentThemeProperties, value, value) }; - } - return { [key]: value }; -} - -function transformStyles(initialStyles) { - return (context) => { - let transformedStyles = {}; - - const mq = facepaint( - context.theme.breakpoints.map((bp) => { - return `@media (min-width: ${bp})`; - }) - ); - - mq( - Array.isArray(initialStyles) ? merge(...initialStyles) : initialStyles - ).forEach((styles) => { - Object.keys(styles).forEach((key) => { - const value = styles[key]; - if (isObject(value)) { - transformedStyles[key] = transformStyles(value)(context); - } else { - transformedStyles = { - ...transformedStyles, - ...transformStyle(key, value, context), - }; - } - }); - }); - - return transformedStyles; - }; -} - -module.exports = transformStyles; diff --git a/readme.md b/readme.md index 03388fa..0dd434c 100644 --- a/readme.md +++ b/readme.md @@ -193,7 +193,7 @@ function Component() { ##### Theme Lookup -Just like [theme-ui](https://theme-ui.com/), values passed to CSS properties are automatically translated from the theme based on a [lookup map](https://github.com/dburles/mystical/blob/master/src/lib/themeTokens.js), and will default to the literal value if there's no match. +Just like [theme-ui](https://theme-ui.com/), values passed to CSS properties are automatically translated from the theme based on a [lookup table](https://github.com/dburles/mystical/blob/master/private/themeTokens.js), and will default to the literal value if there's no match. ##### Dot Properties @@ -254,26 +254,31 @@ function Component() { #### MysticalProvider -Your application must be wrapped with the `MysticalProvider` component: +Provides the theme context, this is required for Mystical to function. -```js -import MysticalProvider from "mystical/MysticalProvider.js"; - -function App() { - return ...; -} -``` +Parameters: -It accepts the following props: - -- theme – The theme object -- options (optional) +- theme: The theme object. +- options: Options (optional) + - `darkModeOff` = `false`: When enabled, dark mode styles are ignored and not added to the output. + - `darkModeForcedBoundary` = `false`: When enabled, Mystical also adds dark mode styles targeting `data-mystical-color-mode="dark"`. This can be useful for development and visual testing environments (such as [Storybook](https://storybook.js.org/)), or for forcing a certain page into dark mode regardless of user system preferences. ```js +import MysticalProvider from "mystical/MysticalProvider.js"; + +// (Optional, defaults shown). const options = { - // Defaults: - usePrefersColorScheme: true, // Sets color mode based on system preferences + darkModeOff: false, + darkModeForcedBoundary: false, }; + +function App() { + return ( + + ... + + ); +} ``` #### Global @@ -404,7 +409,7 @@ import darkColorMode from "mystical/darkColorMode.js"; function Component() { return (
- This text is black in 'default' more and white in 'dark' mode. + This text is black in light mode and white in dark mode.
); } diff --git a/snapshots/transformStyles-colors.json b/snapshots/transformStyles-colors.json deleted file mode 100644 index b784bbb..0000000 --- a/snapshots/transformStyles-colors.json +++ /dev/null @@ -1 +0,0 @@ -{"borderColor":"var(--colors-orange-500)","color":"var(--colors-orange-500)"} \ No newline at end of file diff --git a/test/lib/ReactHookTest.mjs b/test-utils/ReactHookTest.mjs similarity index 100% rename from test/lib/ReactHookTest.mjs rename to test-utils/ReactHookTest.mjs diff --git a/test/lib/theme.mjs b/test-utils/theme.mjs similarity index 100% rename from test/lib/theme.mjs rename to test-utils/theme.mjs diff --git a/test/customProperties.test.mjs b/test/customProperties.test.mjs deleted file mode 100644 index 0f8bc2c..0000000 --- a/test/customProperties.test.mjs +++ /dev/null @@ -1,68 +0,0 @@ -import TestDirector from "test-director/TestDirector.mjs"; -import assert from "assert"; -import customProperties from "../private/customProperties.js"; - -export default function (tests) { - tests.add("customProperties", async () => { - const tests = new TestDirector(); - - tests.add("self referencing", async () => { - const colors = { - brand: { - primary: "emerald.500", - }, - emerald: { - 50: "#ecfdf5", - 100: "#d1fae5", - 200: "#a7f3d0", - 300: "#6ee7b7", - 400: "#34d399", - 500: "#10b981", - 600: "#059669", - 700: "#047857", - 800: "#065f46", - 900: "#064e3b", - }, - }; - - const result = customProperties(colors, "colors"); - assert.strictEqual( - result["--colors-brand-primary"], - colors.emerald["500"] - ); - }); - - tests.add("self referencing (array)", async () => { - const colors = { - brand: { - primary: ["emerald.500", "emerald.100"], - }, - emerald: { - 50: "#ecfdf5", - 100: "#d1fae5", - 200: "#a7f3d0", - 300: "#6ee7b7", - 400: "#34d399", - 500: "#10b981", - 600: "#059669", - 700: "#047857", - 800: "#065f46", - 900: "#064e3b", - }, - }; - - const result = customProperties(colors, "colors"); - - assert.strictEqual( - result["--colors-brand-primary-0"], - colors.emerald["500"] - ); - assert.strictEqual( - result["--colors-brand-primary-1"], - colors.emerald["100"] - ); - }); - - await tests.run(true); - }); -} diff --git a/test/lib/snapshotPath.mjs b/test/lib/snapshotPath.mjs deleted file mode 100644 index 8c89a22..0000000 --- a/test/lib/snapshotPath.mjs +++ /dev/null @@ -1,3 +0,0 @@ -export default function snapshotPath(file) { - return "./snapshots/" + file; -} diff --git a/test/mergeModifiers.test.mjs b/test/mergeModifiers.test.mjs deleted file mode 100644 index 058656c..0000000 --- a/test/mergeModifiers.test.mjs +++ /dev/null @@ -1,80 +0,0 @@ -import assert from "assert"; -import mergeModifiers from "../private/mergeModifiers.js"; -import TestDirector from "test-director/TestDirector.mjs"; - -const modifiers = { - default: { - fontFamily: "heading", - }, - size: { - small: { - fontSize: 3, - }, - large: { - fontSize: 5, - }, - }, -}; - -const modifiers2 = { - default: { - title: { fontFamily: "heading" }, - subtitle: { fontFamily: "body" }, - }, - size: { - small: { - title: { fontSize: 3 }, - subtitle: { fontSize: 0 }, - }, - large: { - title: { fontSize: 5 }, - subtitle: { fontSize: 2 }, - }, - }, -}; - -export default function (tests) { - tests.add("mergeModifiers", async () => { - const tests = new TestDirector(); - - tests.add("basic", async () => { - const modifierStyles = mergeModifiers({ size: "small" }, modifiers); - - assert.strictEqual(Object.keys(modifierStyles).length, 2); - assert.strictEqual(modifierStyles.fontFamily, "heading"); - assert.strictEqual(modifierStyles.fontSize, 3); - }); - - tests.add("basic with customModifiers", async () => { - const modifierStyles = mergeModifiers({ size: "small" }, modifiers, { - fontSize: 4, - }); - - assert.strictEqual(Object.keys(modifierStyles).length, 2); - assert.strictEqual(modifierStyles.fontFamily, "heading"); - assert.strictEqual(modifierStyles.fontSize, 4); - }); - - tests.add("multiple elements", async () => { - const modifierStyles = mergeModifiers({ size: "small" }, modifiers2); - - assert.strictEqual(Object.keys(modifierStyles).length, 2); - assert.strictEqual(modifierStyles.title.fontFamily, "heading"); - assert.strictEqual(modifierStyles.subtitle.fontFamily, "body"); - assert.strictEqual(modifierStyles.title.fontSize, 3); - assert.strictEqual(modifierStyles.subtitle.fontSize, 0); - }); - - tests.add("multiple elements with customModifiers", async () => { - const modifierStyles = mergeModifiers({ size: "small" }, modifiers2, { - title: { - fontSize: 4, - }, - }); - - assert.strictEqual(modifierStyles.title.fontSize, 4); - }); - - await tests.run(true); - }); -} diff --git a/test/run.mjs b/test/run.mjs deleted file mode 100644 index 40ac776..0000000 --- a/test/run.mjs +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-disable react-hooks/rules-of-hooks */ -import TestDirector from "test-director/TestDirector.mjs"; -import customProperties from "./customProperties.test.mjs"; -import mergeModifiers from "./mergeModifiers.test.mjs"; -import transformStyles from "./transformStyles.test.mjs"; -import useTheme from "./useTheme.test.mjs"; - -const tests = new TestDirector(); -transformStyles(tests); -mergeModifiers(tests); -customProperties(tests); -useTheme(tests); -tests.run(); diff --git a/test/transformStyles.test.mjs b/test/transformStyles.test.mjs deleted file mode 100644 index c1f1eb7..0000000 --- a/test/transformStyles.test.mjs +++ /dev/null @@ -1,25 +0,0 @@ -import snapshot from "snapshot-assertion/assertSnapshot.mjs"; -import TestDirector from "test-director/TestDirector.mjs"; -import transformStyles from "../private/transformStyles.js"; -import snapshotPath from "./lib/snapshotPath.mjs"; -import theme from "./lib/theme.mjs"; - -export default function (tests) { - tests.add("transformStyles", async () => { - const tests = new TestDirector(); - - tests.add("colors", async () => { - const styles = transformStyles({ - borderColor: "orange.500", - color: "orange.500", - })({ theme }); - - await snapshot( - JSON.stringify(styles), - snapshotPath("transformStyles-colors.json") - ); - }); - - await tests.run(true); - }); -} diff --git a/test/useTheme.test.mjs b/test/useTheme.test.mjs deleted file mode 100644 index e94719b..0000000 --- a/test/useTheme.test.mjs +++ /dev/null @@ -1,79 +0,0 @@ -import assert from "assert"; -import useTheme from "../useTheme.js"; -import { act, create } from "react-test-renderer"; -import { createElement } from "react"; -import ReactHookTest from "./lib/ReactHookTest.mjs"; -import theme from "./lib/theme.mjs"; -import MysticalProvider from "../MysticalProvider.js"; -import TestDirector from "test-director/TestDirector.mjs"; - -export default function (tests) { - tests.add("useTheme", async () => { - const tests = new TestDirector(); - - tests.add("useTheme: reads values", async () => { - const hookResults = []; - - act(() => { - create( - createElement( - MysticalProvider, - { theme }, - createElement(ReactHookTest, { - hook() { - return useTheme("colors", "emerald.500"); - }, - results: hookResults, - }) - ) - ); - }); - - assert.strictEqual(hookResults[0], theme.colors.emerald["500"]); - }); - - tests.add("useTheme: retains values that aren't found", async () => { - const hookResults = []; - - act(() => { - create( - createElement( - MysticalProvider, - { theme }, - createElement(ReactHookTest, { - hook() { - return useTheme("colors", "emerald.1000"); - }, - results: hookResults, - }) - ) - ); - }); - - assert.strictEqual(hookResults[0], "emerald.1000"); - }); - - tests.add("useTheme: self referencing values are transformed", async () => { - const hookResults = []; - - act(() => { - create( - createElement( - MysticalProvider, - { theme }, - createElement(ReactHookTest, { - hook() { - return useTheme("colors", "brand.primary"); - }, - results: hookResults, - }) - ) - ); - }); - - assert.strictEqual(hookResults[0], theme.colors.emerald["500"]); - }); - - await tests.run(true); - }); -} diff --git a/useTheme.test.mjs b/useTheme.test.mjs new file mode 100644 index 0000000..292ab8c --- /dev/null +++ b/useTheme.test.mjs @@ -0,0 +1,74 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import assert from "node:assert/strict"; +import useTheme from "./useTheme.js"; +import { act, create } from "react-test-renderer"; +import { createElement } from "react"; +import ReactHookTest from "./test-utils/ReactHookTest.mjs"; +import theme from "./test-utils/theme.mjs"; +import MysticalProvider from "./MysticalProvider.js"; +import test from "node:test"; + +test("useTheme", async (t) => { + await t.test("useTheme: reads values", () => { + const hookResults = []; + + act(() => { + create( + createElement( + MysticalProvider, + { theme }, + createElement(ReactHookTest, { + hook() { + return useTheme("colors", "emerald.500"); + }, + results: hookResults, + }) + ) + ); + }); + + assert.equal(hookResults[0], theme.colors.emerald["500"]); + }); + + await t.test("useTheme: retains values that aren't found", () => { + const hookResults = []; + + act(() => { + create( + createElement( + MysticalProvider, + { theme }, + createElement(ReactHookTest, { + hook() { + return useTheme("colors", "emerald.1000"); + }, + results: hookResults, + }) + ) + ); + }); + + assert.equal(hookResults[0], "emerald.1000"); + }); + + await t.test("useTheme: self referencing values are transformed", () => { + const hookResults = []; + + act(() => { + create( + createElement( + MysticalProvider, + { theme }, + createElement(ReactHookTest, { + hook() { + return useTheme("colors", "brand.primary"); + }, + results: hookResults, + }) + ) + ); + }); + + assert.equal(hookResults[0], theme.colors.emerald["500"]); + }); +});