generated from MetaMask/metamask-module-template
-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: adds tests to design-system-tailwind-preset for colors and ind…
…ex (#51) ## **Description** This PR adds unit tests for the Tailwind preset package to ensure that all CSS color variables used in the preset align with those defined in the `@metamask/design-tokens` package. Specifically, it verifies: - CSS variables defined in the preset exist in the design tokens stylesheet - CSS variables that aren't defined in the preset but exist in the stylesheet - Filtering using starts with prefix e.g. `--color` for color object - Ensuring that no missing or incorrect CSS variables exist, preventing breakage due to mismatched or non-existent variables. - Tailwind shortened class names work for text, background and border. - Tailwind preset configuration is correct This change improves the reliability of the Tailwind preset by ensuring consistency with the design tokens. ## **Related issues** Fixes: #48 ## **Manual testing steps** 1. Pull this branch. 2. Run `yarn` in the root of the `metamask-design-system`. 3. Run `yarn workspace @metamask/design-system-tailwind-preset jest` to confirm that tests pass. 4. In `colors.ts`, change a CSS variable to one that doesn't exist in the `design-tokens` package. 5. Run `yarn workspace @metamask/design-system-tailwind-preset jest` to confirm the tests fail. 6. In `node_modules/@metamask/design-tokens/dist/styles.css`, add a color token `--color-random: #000000`. 7. Save the file. 8. Run `yarn workspace @metamask/design-system-tailwind-preset jest` to confirm the tests fail. ## **Screenshots/Recordings** ### **After** The recording demonstrates tests validating the stylesheet CSS against a preset color object and the Tailwind preset configuration. `colors.test.ts` https://github.com/user-attachments/assets/9aa006b0-4a39-4e00-8174-64acdd88bcf9 `index.test.ts` https://github.com/user-attachments/assets/321f274d-03e9-47c3-9cc1-85ad7ab189b0 100% test coverage <img width="756" alt="Screenshot 2024-10-17 at 4 30 57 PM" src="https://github.com/user-attachments/assets/94bd9fee-674d-46e1-bd49-8e915aa94363"> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) - [x] I've completed the PR template to the best of my ability - [x] I've included tests to verify the changes - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the correct labels on the PR ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR and validated the changes. - [ ] All acceptance criteria described in the related issue have been met. - [ ] Appropriate testing evidence (screenshots, recordings) has been provided.
- Loading branch information
1 parent
9263aa0
commit d86723d
Showing
7 changed files
with
215 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
packages/design-system-tailwind-preset/scripts/testUtils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
import postcss from 'postcss'; | ||
|
||
/** | ||
* Parses the design-tokens stylesheet and extracts CSS variable names based on provided prefixes. | ||
* | ||
* This utility function reads the `styles.css` file from the `@metamask/design-tokens` package, | ||
* parses its content using PostCSS, and collects all CSS variables that match any of the specified prefixes. | ||
* | ||
* @param prefixes - (Optional) An array of prefixes to filter CSS variables. Defaults to ['--']. | ||
* For example, ['--color'] will only collect variables that start with '--color'. | ||
* @returns A Promise that resolves to a Set containing the filtered CSS variable names. | ||
*/ | ||
export const getDesignTokenVariables = async ( | ||
prefixes: string[] = ['--'], | ||
): Promise<Set<string>> => { | ||
// Resolve the absolute path to the styles.css file in the design-tokens package | ||
const stylesheetPath = path.resolve( | ||
__dirname, | ||
'../../../node_modules/@metamask/design-tokens/dist/styles.css', | ||
); | ||
|
||
// Asynchronously read the content of the stylesheet | ||
const cssContent = await fs.promises.readFile(stylesheetPath, 'utf-8'); | ||
|
||
// Parse the CSS content using PostCSS to create an Abstract Syntax Tree (AST) | ||
const parsedRoot = postcss.parse(cssContent); | ||
|
||
// Initialize a Set to store the filtered CSS variable names | ||
const variables = new Set<string>(); | ||
|
||
// Traverse all CSS declarations in the AST | ||
parsedRoot.walkDecls((decl) => { | ||
const { prop } = decl; | ||
// Check if the property name starts with any of the specified prefixes | ||
if (prefixes.some((prefix) => prop.startsWith(prefix))) { | ||
variables.add(prop); // Add the matching variable to the Set | ||
} | ||
}); | ||
|
||
return variables; // Return the Set of filtered CSS variable names | ||
}; | ||
|
||
/** | ||
* Recursively traverses an object(e.g. colors, shadows, typography) to collect all CSS variable values. | ||
* | ||
* This utility function traverses a given object, searches for strings that reference CSS variables | ||
* using the `var(--variable-name)` syntax, and collects the names of these variables. | ||
* | ||
* @param obj - The object to traverse. Typically, this would be the preset object containing the CSS variables. | ||
* @returns An array of extracted CSS variable names. | ||
*/ | ||
export const collectCssVariables = (obj: Record<string, unknown>): string[] => { | ||
const variables: string[] = []; // Initialize an array to store the found CSS variable names | ||
|
||
/** | ||
* Helper function to recursively traverse the object. | ||
* | ||
* @param current - The current value being traversed. Can be of any type. | ||
*/ | ||
const traverse = (current: unknown) => { | ||
if (typeof current === 'string') { | ||
// Use a regular expression to match the CSS variable pattern | ||
const match = current.match(/var\((--[^)]+)\)/u); | ||
if (match) { | ||
variables.push(match[1]); // Extract and store the variable name | ||
} | ||
} else if (typeof current === 'object' && current !== null) { | ||
// If the current value is an object, recursively traverse its properties | ||
Object.values(current).forEach((value) => traverse(value)); | ||
} | ||
// Non-string and non-object values are ignored | ||
}; | ||
|
||
traverse(obj); // Start the traversal with the root object | ||
return variables; // Return the array of collected CSS variable names | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,58 @@ | ||
import { | ||
getDesignTokenVariables, | ||
collectCssVariables, | ||
} from '../scripts/testUtils'; | ||
import { colors } from './colors'; | ||
|
||
describe('Colors', () => { | ||
// TODO: Implement tests for colors https://github.com/MetaMask/metamask-design-system/issues/48 | ||
it('should have a dummy test', () => { | ||
expect(colors).toBeDefined(); | ||
describe('Color Preset', () => { | ||
// Collect all CSS variables used in the 'colors' object | ||
const usedVariables = collectCssVariables(colors); | ||
|
||
// Define CSS variables to ignore in the unused variables check | ||
const ignoreList: string[] = [ | ||
'--color-shadow-default', // used in shadow.ts | ||
'--color-shadow-primary', // used in shadow.ts | ||
'--color-shadow-error', // used in shadow.ts | ||
'--color-flask-default', // not needed for tailwind | ||
'--color-flask-inverse', // not needed for tailwind | ||
]; | ||
|
||
/** | ||
* Test to ensure that all CSS variables used in the 'colors' object | ||
* are defined in the @metamask/design-tokens package. | ||
*/ | ||
it('should use only CSS variables that exist in @metamask/design-tokens', async () => { | ||
// Retrieve all design token variables that start with '--color' | ||
const designTokens = await getDesignTokenVariables(['--color']); | ||
|
||
// Identify any used variables that are missing from the design tokens | ||
const missingVariables = usedVariables.filter( | ||
(varName) => !designTokens.has(varName), | ||
); | ||
|
||
// Expect no missing variables | ||
expect(missingVariables).toHaveLength(0); | ||
}); | ||
|
||
/** | ||
* Test to ensure that there are no unused CSS variables in the | ||
* design-tokens package that are not used in the 'colors' object, | ||
* excluding those in the ignore list. | ||
*/ | ||
it('should not have unused CSS variables in @metamask/design-tokens', async () => { | ||
// Retrieve all design token variables that start with '--color' | ||
const designTokens = await getDesignTokenVariables(['--color']); | ||
|
||
// Create sets for used variables and ignored variables for efficient lookup | ||
const usedSet = new Set(usedVariables); | ||
const ignoredSet = new Set(ignoreList); | ||
|
||
// Identify design token variables that are neither used nor ignored | ||
const unusedVariables = Array.from(designTokens).filter( | ||
(varName) => !usedSet.has(varName) && !ignoredSet.has(varName), | ||
); | ||
|
||
// Expect no unused variables | ||
expect(unusedVariables).toHaveLength(0); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,75 @@ | ||
import tailwindConfig from '.'; | ||
import { colors } from './colors'; | ||
|
||
describe('Tailwind Preset', () => { | ||
// TODO: Implement tests for Tailwind preset https://github.com/MetaMask/metamask-design-system/issues/48 | ||
it('should have a dummy test', () => { | ||
/** | ||
* Structure | ||
*/ | ||
|
||
it('default export is tailwindConfig', () => { | ||
expect(tailwindConfig).toBeDefined(); | ||
}); | ||
|
||
it('configuration has correct structure', () => { | ||
expect(tailwindConfig).toHaveProperty('content'); | ||
expect(tailwindConfig).toHaveProperty('theme.extend'); | ||
expect(tailwindConfig).toHaveProperty('plugins'); | ||
}); | ||
|
||
/** | ||
* Colors | ||
*/ | ||
|
||
it('colors are correctly imported and spread', () => { | ||
expect(tailwindConfig.theme?.extend?.colors).toStrictEqual( | ||
expect.objectContaining(colors), | ||
); | ||
}); | ||
|
||
it('textColor function incorporates theme colors(e.g. text-primary-default, text-error-default) and text colors(e.g. text-default, text-muted)', () => { | ||
const textColorFn = tailwindConfig.theme?.extend?.textColor as (options: { | ||
theme: (path: string) => unknown; | ||
}) => Record<string, string>; | ||
const result = textColorFn({ | ||
theme: (path: string) => (path === 'colors' ? colors : {}), | ||
}); | ||
expect(result).toStrictEqual( | ||
expect.objectContaining({ | ||
...colors, | ||
...colors.text, | ||
}), | ||
); | ||
}); | ||
|
||
it('backgroundColor function incorporates theme colors(e.g. bg-primary-default, bg-error-default) and background colors(e.g. bg-default, bg-muted)', () => { | ||
const bgColorFn = tailwindConfig.theme?.extend | ||
?.backgroundColor as (options: { | ||
theme: (path: string) => unknown; | ||
}) => Record<string, string>; | ||
const result = bgColorFn({ | ||
theme: (path: string) => (path === 'colors' ? colors : {}), | ||
}); | ||
expect(result).toStrictEqual( | ||
expect.objectContaining({ | ||
...colors, | ||
...colors.background, | ||
}), | ||
); | ||
}); | ||
|
||
it('borderColor function incorporates theme colors(e.g. border-primary-default, border-error-default) and border colors(e.g. border-default, border-muted)', () => { | ||
const borderColorFn = tailwindConfig.theme?.extend | ||
?.borderColor as (options: { | ||
theme: (path: string) => unknown; | ||
}) => Record<string, string>; | ||
const result = borderColorFn({ | ||
theme: (path: string) => (path === 'colors' ? colors : {}), | ||
}); | ||
expect(result).toStrictEqual( | ||
expect.objectContaining({ | ||
...colors, | ||
...colors.border, | ||
}), | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters