diff --git a/packages/combobox/src/Combobox/Combobox.spec.tsx b/packages/combobox/src/Combobox/Combobox.spec.tsx index cd3f66b275..396371e96d 100644 --- a/packages/combobox/src/Combobox/Combobox.spec.tsx +++ b/packages/combobox/src/Combobox/Combobox.spec.tsx @@ -29,6 +29,8 @@ import { testif, } from '../utils/ComboboxTestUtils'; +import { Combobox } from './Combobox'; + /** * Tests */ @@ -70,6 +72,8 @@ describe('packages/combobox', () => { : testif(select === 'multiple')(name, fn); describe('Basic rendering', () => { + test('spreads rest', () => expect(Combobox).toSpreadRest()); + // Label prop test('Label is rendered', () => { const { labelEl } = renderCombobox(select, { label: 'Some label' }); diff --git a/packages/date-picker/src/DatePicker/DatePicker.spec.tsx b/packages/date-picker/src/DatePicker/DatePicker.spec.tsx index 9c49d7485f..efecdcd64e 100644 --- a/packages/date-picker/src/DatePicker/DatePicker.spec.tsx +++ b/packages/date-picker/src/DatePicker/DatePicker.spec.tsx @@ -71,6 +71,8 @@ describe('packages/date-picker', () => { /// Note: Many rendering tests should be handled by Chromatic describe('Input', () => { + test('spreads rest', () => expect(DatePicker).toSpreadRest()); + test('renders label', () => { const { getByText } = render(); const label = getByText('Label'); diff --git a/packages/text-input/src/TextInput/TextInput.spec.tsx b/packages/text-input/src/TextInput/TextInput.spec.tsx index bc50fa648c..4be380094e 100644 --- a/packages/text-input/src/TextInput/TextInput.spec.tsx +++ b/packages/text-input/src/TextInput/TextInput.spec.tsx @@ -47,12 +47,27 @@ describe('packages/text-input', () => { expect(results).toHaveNoViolations(); }); }); + test(`renders ${defaultProps.label} as the input label and ${defaultProps.description} as the description`, () => { const { label, description } = renderTextInput(defaultProps); expect(label?.innerHTML).toContain(defaultProps.label); expect(description?.innerHTML).toContain(defaultProps.description); }); + test('allows external label', () => { + const { getByTestId } = render( + <> + + + , + ); + + const input = getByTestId('input'); + expect(input).toBeLabelled(); + }); + test(`renders ${defaultProps.placeholder} as placeholder text`, () => { const { getByPlaceholderText } = renderTextInput(defaultProps); expect(getByPlaceholderText(defaultProps.placeholder)).toBeVisible(); diff --git a/tools/jest-matchers/README.md b/tools/jest-matchers/README.md new file mode 100644 index 0000000000..7fb8e93d15 --- /dev/null +++ b/tools/jest-matchers/README.md @@ -0,0 +1,19 @@ +# Jest Matchers + +![npm (scoped)](https://img.shields.io/npm/v/@leafygreen-ui/jest-matchers.svg) + +#### [View on MongoDB.design](https://www.mongodb.design/component/jest-matchers/example/) + +## Installation + +### Yarn + +```shell +yarn add @leafygreen-ui/jest-matchers +``` + +### NPM + +```shell +npm install @leafygreen-ui/jest-matchers +``` diff --git a/tools/jest-matchers/package.json b/tools/jest-matchers/package.json new file mode 100644 index 0000000000..472c5c0f56 --- /dev/null +++ b/tools/jest-matchers/package.json @@ -0,0 +1,40 @@ +{ + "name": "@lg-tools/jest-matchers", + "version": "0.0.1", + "description": "LeafyGreen UI Kit Jest Matchers", + "main": "./dist/index.js", + "module": "./dist/esm/index.js", + "types": "./dist/index.d.ts", + "license": "Apache-2.0", + "scripts": { + "build": "lg-internal-build-package", + "tsc": "tsc --build tsconfig.json", + "create-matcher": "npx ts-node ./scripts/createNewMatcher.ts" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "chalk": "4.1.2", + "lodash": "^4.17.21" + }, + "devDependencies": { + "@lg-tools/build": "0.3.0", + "commander": "^11.1.0", + "fs-extra": "^11.1.1", + "jsdom": "^22.1.0" + }, + "peerDependencies": { + "@testing-library/react": "^14.0.0", + "@types/jest": "^29.5.0", + "jest": "^29.6.0" + }, + "homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/packages/jest-matchers", + "repository": { + "type": "git", + "url": "https://github.com/mongodb/leafygreen-ui" + }, + "bugs": { + "url": "https://jira.mongodb.org/projects/PD/summary" + } +} diff --git a/tools/jest-matchers/scripts/createNewMatcher.ts b/tools/jest-matchers/scripts/createNewMatcher.ts new file mode 100644 index 0000000000..a3cf6e4f2a --- /dev/null +++ b/tools/jest-matchers/scripts/createNewMatcher.ts @@ -0,0 +1,60 @@ +import chalk from 'chalk'; +import { Command } from 'commander'; +import fse from 'fs-extra'; +import { camelCase, lowerFirst } from 'lodash'; +import path from 'path'; + +const cli = new Command(); +cli + .argument('', 'The name of the new matcher') + .action(createNewMatcher) + .parse(); + +function createNewMatcher(matcherName: string) { + matcherName = lowerFirst(camelCase(matcherName)); + + if (!matcherName.startsWith('to')) { + console.warn( + chalk.yellow( + `Matcher names should start with the word "to". Received \`${matcherName}\``, + ), + ); + } + + const matchersDir = path.resolve(__dirname, '../src/matchers'); + + /** Create the matcher file */ + const matchersFilePath = path.resolve(matchersDir, matcherName + '.ts'); + const matcherFileTemplate = ` +import { createMatcher } from '../utils/createMatcher'; + +export const ${matcherName} = createMatcher(function _${matcherName}() { + return { + pass: true, + message: () => '', + }; +}); +`; + fse.writeFileSync(matchersFilePath, matcherFileTemplate); + + /** Create the test file */ + + const testFilePath = path.resolve( + __dirname, + '../src/tests', + matcherName + '.spec.ts', + ); + + const testFileTemplate = ` +describe('tools/jest-matchers/.${matcherName}', () => { + test.todo('') +}) +`; + fse.writeFileSync(testFilePath, testFileTemplate); + + /** Update the matchers index file */ + const indexFilePath = path.resolve(matchersDir, 'index.ts'); + let indexContents = fse.readFileSync(indexFilePath, 'utf-8'); + indexContents += `export { ${matcherName} } from './${matcherName}';\n`; + fse.writeFileSync(indexFilePath, indexContents); +} diff --git a/tools/jest-matchers/scripts/tsconfig.json b/tools/jest-matchers/scripts/tsconfig.json new file mode 100644 index 0000000000..d8ad23ab66 --- /dev/null +++ b/tools/jest-matchers/scripts/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "noEmit": true, + "tsBuildInfoFile": "./tsconfig.tsbuildinfo", + "incremental": true, + "target": "ES5", + "jsx": "react", + "allowJs": true, + "pretty": true, + "strictNullChecks": true, + "noUnusedLocals": false, + "esModuleInterop": true, + "strict": true, + "allowSyntheticDefaultImports": true, + "moduleResolution": "node", + "baseUrl": ".", + "skipLibCheck": true, + "resolveJsonModule": true, + } +} \ No newline at end of file diff --git a/tools/jest-matchers/src/index.ts b/tools/jest-matchers/src/index.ts new file mode 100644 index 0000000000..67df2d899f --- /dev/null +++ b/tools/jest-matchers/src/index.ts @@ -0,0 +1,3 @@ +import * as matchers from './matchers'; + +expect.extend(matchers); diff --git a/tools/jest-matchers/src/matchers/index.ts b/tools/jest-matchers/src/matchers/index.ts new file mode 100644 index 0000000000..e01e47eac2 --- /dev/null +++ b/tools/jest-matchers/src/matchers/index.ts @@ -0,0 +1,2 @@ +export { toBeLabelled } from './toBeLabelled'; +export { toSpreadRest } from './toSpreadRest'; diff --git a/tools/jest-matchers/src/matchers/toBeLabelled.ts b/tools/jest-matchers/src/matchers/toBeLabelled.ts new file mode 100644 index 0000000000..344bed5b3d --- /dev/null +++ b/tools/jest-matchers/src/matchers/toBeLabelled.ts @@ -0,0 +1,57 @@ +import { isNull } from 'lodash'; + +import { createMatcher } from '../utils/createMatcher'; + +const isValidString = (str: any): str is string => + str && typeof str === 'string' && str.length > 0; + +/** + * Returns whether the provided element has at least one of the following: + * - a `label` attribute + * - an `aria-label` attribute + * - an `aria-labelledby` attribute with a valid associated element + * - an associated element `