Skip to content

Commit

Permalink
chore(themed): add token CSS variable test helper (#9860)
Browse files Browse the repository at this point in the history
**Related Issue:** #7180 

## Summary

Includes helpers which assist developers in testing and theming
component tokens as CSS variables.

### EXAMPLE USAGE:
 
#### Demo page

```html
<demo-theme tokens="--calcite-button-background-color, --calcite-button-border-color, --calcite-button-text-color"
    ><calcite-button kind="inverse" scale="l"> Button </calcite-button
></demo-theme>
```

#### Storybook / Chromatic

```ts
export const theming_TestOnly = (): string =>
  html` <style>
      .container {
        ${setCSSVariables([
        "--calcite-button-background-color",
        "--calcite-button-border-color",
        "--calcite-button-text-color",
      ])}
      }
    </style>
    <div class="container">
        <calcite-button kind="inverse" scale="l"> Button </calcite-button>
   </div>
`;
```

#### E2E

```ts
describe("theme", () => {
  themed(`<calcite-button kind="inverse" scale="l"> Button </calcite-button>`,  {
    "--calcite-button-background-color": {
      shadowSelector: ".button",
      targetProp: "backgroundColor",
    },
    "--calcite-button-text-color": {
      shadowSelector: ".button",
      targetProp: "color",
    }
    "--calcite-accordion-border-color": {
      shadowSelector: ".button",
      targetProp: "borderColor",
    },
  });
});
```
  • Loading branch information
alisonailea authored and github-actions[bot] committed Jul 30, 2024
1 parent 22ebe66 commit bcedcf7
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 19 deletions.
112 changes: 112 additions & 0 deletions packages/calcite-components/src/demos/_assets/demo-theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
*
* Sets the value of a CSS variable to a test value.
* This is useful for testing themed components.
*
* @param token - the token as a CSS variable
* @returns string - the new value for the token
*/
export function getTokenValue(token: string): string {
const tokenValueMap = {
background$: "rgb(252, 244, 52)",
"text-color$": "rgb(239,118,39)",
"border-color$": "rgb(156, 89, 209)",
"background-color$": "rgb(44, 44, 44)",
color$: "rgb(0, 191, 255)",
highlighted$: "rgb(255, 105, 180)",
selected$: "rgb(255, 255, 255)",
shadow$:
"rgb(255, 255, 255) 0px 0px 0px 4px, rgb(255, 105, 180) 0px 0px 0px 5px inset, rgb(0, 191, 255) 0px 0px 0px 9px",
"z-index$": "42",
"(size|space)$": "42px",
} as const;

const match = Object.entries(tokenValueMap).find(([regexStr]) => {
return new RegExp(regexStr, "g").test(token);
});

if (!match) {
console.warn("token not found in tokenValueMap", token);
return tokenValueMap["color$"];
}

return match[1];
}

/*
* @prop tokens - an array of CSS variables
* @returns a string of CSS variables with their new values.
*/
export function setCSSVariables(tokens: string[]): string {
return tokens
.map((token) => {
return `${token}: ${getTokenValue(token)};`;
})
.join("\n");
}

/**
*
* @example
* <demo-theme tokens="
* --calcite-button-background-color,
* --calcite-button-border-color,
* --calcite-button-corner-radius,
* --calcite-button-corner-radius-start-start,
* --calcite-button-corner-radius-start-end,
* --calcite-button-corner-radius-end-start,
* --calcite-button-corner-radius-end-end,
* --calcite-button-loader-color,
* --calcite-button-shadow,
* --calcite-button-text-color,
* --calcite-button-icon-color"
* ><calcite-button kind="inverse" scale="l"> Button </calcite-button></demo-theme>
*/
export class DemoTheme extends HTMLElement {
_slot: HTMLSlotElement;

_el: HTMLElement;

static observedAttributes = ["tokens"];

constructor() {
super();
const shadow = this.attachShadow({ mode: "open" });
const slot = document.createElement("slot");
shadow.append(slot);
this._slot = slot;
if (this._slot.assignedNodes().length === 1 && this._slot.assignedNodes()[0].nodeName.includes("calcite")) {
this._el = this._slot.assignedNodes()[0] as HTMLElement;
}
}

attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
if (newValue !== oldValue && name === "tokens") {
this.updateTheme(newValue);
}
}

updateTheme(newValue: string): void {
if (typeof newValue === "string") {
let tokensList;

try {
tokensList = JSON.parse(newValue);
} catch (error) {
tokensList = newValue.split(",").map((t) => t.trim());
}

if (Array.isArray(tokensList)) {
const stringifiedTheme = setCSSVariables(tokensList);

if (this._el) {
this._el.style.cssText = stringifiedTheme;
} else {
this.setAttribute("style", stringifiedTheme);
}
}
}
}
}

customElements.define("demo-theme", DemoTheme);
4 changes: 4 additions & 0 deletions packages/calcite-components/src/demos/_assets/head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
{
src: "demos/_assets/demo-dom-swapper.js",
},
{
src: "demos/_assets/demo-theme.js",
type: "module",
},
];

const parseTemplate = (text: string): HTMLTemplateElement | null => {
Expand Down
21 changes: 2 additions & 19 deletions packages/calcite-components/src/tests/commonTests/themed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { E2EElement, E2EPage } from "@stencil/core/testing";
import { toHaveNoViolations } from "jest-axe";
import { ElementHandle } from "puppeteer";
import type { RequireExactlyOne } from "type-fest";
import { getTokenValue } from "../utils/cssTokenValues";
import type { ComponentTestSetup } from "./interfaces";
import { getTagAndPage } from "./utils";

Expand Down Expand Up @@ -101,7 +102,7 @@ export function themed(componentTestSetup: ComponentTestSetup, tokens: Component

// Set test values for each token
if (!setTokens[token]) {
setTokens[token] = assignTestTokenThemeValues(token);
setTokens[token] = getTokenValue(token);
}

// Set up styleTargets and testTargets
Expand Down Expand Up @@ -426,21 +427,3 @@ async function assertThemedProps(page: E2EPage, options: TestTarget): Promise<vo
function getStyleString(token: string, prop: string, value: string): string {
return `[${token}:${prop}] ${value}`;
}

/**
*
* Sets the value of a CSS variable to a test value.
* This is useful for testing themed components.
*
* @param token - the token as a CSS variable
* @returns string - the new value for the token
*/
function assignTestTokenThemeValues(token: string): string {
const legacyBackgroundColorToken = token.endsWith("-background");

return token.includes("color") || legacyBackgroundColorToken
? "rgb(0, 191, 255)"
: token.includes("shadow")
? "rgb(255, 255, 255) 0px 0px 0px 4px, rgb(255, 105, 180) 0px 0px 0px 5px inset, rgb(0, 191, 255) 0px 0px 0px 9px"
: `42${token.includes("z-index") ? "" : "px"}`;
}
47 changes: 47 additions & 0 deletions packages/calcite-components/src/tests/utils/cssTokenValues.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
*
* Sets the value of a CSS variable to a test value.
* This is useful for testing themed components.
*
* @param token - the token as a CSS variable
* @returns string - the new value for the token
*/
export function getTokenValue(token: string): string {
const tokenValueMap = {
background$: "rgb(252, 244, 52)",
"text-color$": "rgb(239,118,39)",
"border-color$": "rgb(156, 89, 209)",
"background-color$": "rgb(44, 44, 44)",
color$: "rgb(0, 191, 255)",
highlighted$: "rgb(255, 105, 180)",
selected$: "rgb(255, 255, 255)",
shadow$:
"rgb(255, 255, 255) 0px 0px 0px 4px, rgb(255, 105, 180) 0px 0px 0px 5px inset, rgb(0, 191, 255) 0px 0px 0px 9px",
"z-index$": "42",
"(size|space)$": "42px",
} as const;

const match = Object.entries(tokenValueMap).find(([regexStr]) => {
return new RegExp(regexStr, "g").test(token);
});

if (!match) {
console.warn("token not found in tokenValueMap", token);
return tokenValueMap["color$"];
}

return match[1];
}

/**
*
* @param tokens - an array of CSS variables
* @returns a string of CSS variables with their new values.
*/
export function setCSSVariables(tokens: string[]): string {
return tokens
.map((token) => {
return `${token}: ${getTokenValue(token)};`;
})
.join("\n");
}

0 comments on commit bcedcf7

Please sign in to comment.