Skip to content

Commit

Permalink
Add Create Selector function (#49)
Browse files Browse the repository at this point in the history
* Add createSelector function

* Prepare jest-selectors release 1.4.0

* Fix selectors types

* Prepare jest-selectors release 1.4.1

* Update components to use createSelector

* Update components changelog
  • Loading branch information
asimonok authored Jul 2, 2024
1 parent 44d6717 commit 6838e64
Show file tree
Hide file tree
Showing 13 changed files with 126 additions and 52 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## 2.7.0 (IN PROGRESS)

### Features

- Update to use auto-apply selectors (#49)

## 2.6.0 (2024-06-12)

### Features
Expand Down
2 changes: 1 addition & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"@emotion/css": "^11.11.2",
"@grafana/data": "^11.1.0",
"@grafana/ui": "^11.1.0",
"@volkovlabs/jest-selectors": "^1.4.1",
"classnames": "^2.5.1",
"rc-slider": "^10.5.0",
"rc-tooltip": "^6.2.0"
Expand All @@ -30,7 +31,6 @@
"@types/node": "^20.9.1",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@volkovlabs/eslint-config": "^1.0.0",
"@volkovlabs/jest-selectors": "^1.2.0",
"css-loader": "^6.8.1",
"esbuild": "^0.19.5",
"eslint": "^8.53.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/components/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default [
'@grafana/data',
'lodash',
'rc-slider/assets/index.css',
'@volkovlabs/jest-selectors',
],
},
{
Expand All @@ -45,6 +46,7 @@ export default [
'@grafana/data',
'lodash',
'rc-slider/assets/index.css',
'@volkovlabs/jest-selectors',
],
},
];
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { getJestSelectors } from '@volkovlabs/jest-selectors';
import { getJestSelectors, createSelector } from '@volkovlabs/jest-selectors';
import React from 'react';

import { CODE_EDITOR_CONFIG } from '../../constants';
Expand All @@ -14,7 +14,7 @@ type Props = React.ComponentProps<typeof AutosizeCodeEditor>;
* In Test Ids
*/
const InTestIds = {
field: 'data-testid field',
field: createSelector('data-testid field'),
};

/**
Expand All @@ -24,7 +24,7 @@ jest.mock('@grafana/ui', () => ({
...jest.requireActual('@grafana/ui'),
CodeEditor: jest.fn(({ value, onChange, height }) => (
<textarea
data-testid={InTestIds.field}
{...InTestIds.field.apply()}
style={{ height }}
value={value}
onChange={(event) => onChange(event.currentTarget.value)}
Expand Down
16 changes: 10 additions & 6 deletions packages/components/src/components/Collapse/Collapse.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { getJestSelectors } from '@volkovlabs/jest-selectors';
import { getJestSelectors, createSelector } from '@volkovlabs/jest-selectors';
import React from 'react';

import { Collapse } from './Collapse';
Expand All @@ -10,9 +10,9 @@ type Props = React.ComponentProps<typeof Collapse>;
* In Test Ids
*/
const InTestIds = {
header: 'data-testid header',
content: 'data-testid content',
buttonRemove: 'data-testid button-remove',
header: createSelector('data-testid header', 'headerTestId'),
content: createSelector('data-testid content', 'contentTestId'),
buttonRemove: createSelector('data-testid button-remove'),
};

/**
Expand All @@ -27,7 +27,7 @@ describe('Collapse', () => {
* Get Tested Component
*/
const getComponent = (props: Partial<Props>) => {
return <Collapse headerTestId={InTestIds.header} contentTestId={InTestIds.content} {...props} />;
return <Collapse {...InTestIds.header.apply()} {...InTestIds.content.apply()} {...props} />;
};

it('Should expand content', () => {
Expand All @@ -42,7 +42,11 @@ describe('Collapse', () => {
it('Actions should not affect collapse state', () => {
const onToggle = jest.fn();

render(getComponent({ onToggle, actions: <button data-testid={InTestIds.buttonRemove}>remove</button> }));
/**
* New
*/
render(getComponent({ onToggle, actions: <button {...InTestIds.buttonRemove.apply()}>remove</button> }));

fireEvent.click(selectors.buttonRemove());
expect(onToggle).not.toHaveBeenCalled();
});
Expand Down
33 changes: 13 additions & 20 deletions packages/components/src/components/Form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ export const Form = <TValue extends object>({
<CollapsableSection
key={field.fullPath}
label={field.label}
headerDataTestId={TEST_IDS.form.sectionHeader(field.fullPath)}
contentDataTestId={TEST_IDS.form.sectionContent(field.fullPath)}
{...TEST_IDS.form.sectionHeader.apply(field.fullPath)}
{...TEST_IDS.form.sectionContent.apply(field.fullPath)}
contentClassName={styles.section}
isOpen={expanded[field.fullPath] ?? true}
onToggle={(isOpen) =>
Expand Down Expand Up @@ -204,7 +204,7 @@ export const Form = <TValue extends object>({
}
}}
{...field.settings}
aria-label={TEST_IDS.form.fieldSelect(field.fullPath)}
{...TEST_IDS.form.fieldSelect.apply(field.fullPath)}
/>
</FieldComponent>
);
Expand All @@ -214,11 +214,7 @@ export const Form = <TValue extends object>({
const Editor = field.editor;
return (
<FieldComponent {...fieldProps}>
<Editor
value={field.value}
onChange={field.onChange}
data-testid={TEST_IDS.form.fieldCustom(field.fullPath)}
/>
<Editor value={field.value} onChange={field.onChange} {...TEST_IDS.form.fieldCustom.apply(field.fullPath)} />
</FieldComponent>
);
}
Expand All @@ -233,7 +229,7 @@ export const Form = <TValue extends object>({
max={field.max}
step={field.step}
marks={field.marks}
data-testid={TEST_IDS.form.fieldSlider(field.fullPath)}
{...TEST_IDS.form.fieldSlider.apply(field.fullPath)}
/>
</FieldComponent>
);
Expand All @@ -249,7 +245,7 @@ export const Form = <TValue extends object>({
max={field.max}
step={field.step}
marks={field.marks}
sliderAriaLabel={TEST_IDS.form.fieldRangeSlider(field.fullPath)}
{...TEST_IDS.form.fieldRangeSlider.apply(field.fullPath)}
/>
</FieldComponent>
);
Expand All @@ -264,7 +260,7 @@ export const Form = <TValue extends object>({
min={field.min}
max={field.max}
step={field.step}
data-testid={TEST_IDS.form.fieldNumberInput(field.fullPath)}
{...TEST_IDS.form.fieldNumberInput.apply(field.fullPath)}
/>
</FieldComponent>
);
Expand All @@ -281,7 +277,7 @@ export const Form = <TValue extends object>({
field.onChange(color);
}
}}
data-testid={TEST_IDS.form.fieldColor(field.fullPath)}
{...TEST_IDS.form.fieldColor.apply(field.fullPath)}
/>
</FormControl>
</FieldComponent>
Expand All @@ -297,7 +293,7 @@ export const Form = <TValue extends object>({
field.onChange(event.currentTarget.value);
}}
placeholder={field.placeholder}
data-testid={TEST_IDS.form.fieldInput(field.fullPath)}
{...TEST_IDS.form.fieldInput.apply(field.fullPath)}
/>
</FieldComponent>
);
Expand All @@ -308,10 +304,7 @@ export const Form = <TValue extends object>({
return (
<FieldComponent {...fieldProps}>
{fieldProps.disabled ? (
<Input
value={dateTimeFormat(dateTime(field.value), { format })}
data-testid={TEST_IDS.form.fieldDatetime()}
/>
<Input value={dateTimeFormat(dateTime(field.value), { format })} {...TEST_IDS.form.fieldDatetime.apply()} />
) : (
<DateTimePicker
minDate={field.min ? new Date(field.min) : undefined}
Expand All @@ -323,7 +316,7 @@ export const Form = <TValue extends object>({
field.onChange(value.toISOString());
}
}}
data-testid={TEST_IDS.form.fieldDatetime()}
{...TEST_IDS.form.fieldDatetime.apply()}
/>
)}
</FieldComponent>
Expand All @@ -332,7 +325,7 @@ export const Form = <TValue extends object>({

if (field.type === FormFieldType.RADIO) {
return (
<FieldComponent {...fieldProps} data-testid={TEST_IDS.form.fieldRadio(field.fullPath)}>
<FieldComponent {...fieldProps} {...TEST_IDS.form.fieldRadio.apply(field.fullPath)}>
<RadioButtonGroup
value={field.value}
onChange={field.onChange}
Expand All @@ -348,7 +341,7 @@ export const Form = <TValue extends object>({
};

return (
<div data-testid={TEST_IDS.form.root(name)}>
<div {...TEST_IDS.form.root.apply(name)}>
{groupFieldsInRows(fields).map((fields, index) =>
fields.length > 1 && variant === 'inline' ? (
<InlineFieldRow key={index}>{fields.map((field) => renderField(field))}</InlineFieldRow>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { getJestSelectors } from '@volkovlabs/jest-selectors';
import { getJestSelectors, createSelector } from '@volkovlabs/jest-selectors';
import React from 'react';

import { NumberInput } from './NumberInput';
Expand All @@ -13,7 +13,7 @@ type Props = React.ComponentProps<typeof NumberInput>;
* In Test Ids
*/
const InTestIds = {
field: 'data-testid field',
field: createSelector('data-testid field'),
};

describe('Number Input', () => {
Expand All @@ -27,7 +27,7 @@ describe('Number Input', () => {
* Get Component
*/
const getComponent = (props: Partial<Props>) => {
return <NumberInput min={1} max={10} data-testid={InTestIds.field} {...(props as any)} />;
return <NumberInput min={1} max={10} {...InTestIds.field.apply()} {...(props as any)} />;
};

it('Should allow to enter negative value', () => {
Expand Down
26 changes: 14 additions & 12 deletions packages/components/src/constants/tests.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import { createSelector } from '@volkovlabs/jest-selectors';

/**
* Test Identifiers
*/
export const TEST_IDS = {
form: {
root: (name: unknown) => `data-testid form-${name}`,
sectionHeader: (name: unknown) => `data-testid form section-${name}`,
sectionContent: (name: unknown) => `data-testid form section-content-${name}`,
fieldSelect: (name: unknown) => `form field-select-${name}`,
fieldCustom: (name: unknown) => `data-testid form field-custom-${name}`,
fieldSlider: (name: unknown) => `data-testid form field-slider-${name}`,
fieldRangeSlider: (name: unknown) => `form field-range-slider-${name}`,
fieldNumberInput: (name: unknown) => `data-testid form field-number-input-${name}`,
fieldColor: (name: unknown) => `data-testid form field-color-${name}`,
fieldInput: (name: unknown) => `data-testid form field-input-${name}`,
root: createSelector((name: unknown) => `data-testid form-${name}`),
sectionHeader: createSelector((name: unknown) => `data-testid form section-${name}`, 'headerDataTestId'),
sectionContent: createSelector((name: unknown) => `data-testid form section-content-${name}`, 'contentDataTestId'),
fieldSelect: createSelector((name: unknown) => `form field-select-${name}`),
fieldCustom: createSelector((name: unknown) => `data-testid form field-custom-${name}`),
fieldSlider: createSelector((name: unknown) => `data-testid form field-slider-${name}`),
fieldRangeSlider: createSelector((name: unknown) => `form field-range-slider-${name}`, 'sliderAriaLabel'),
fieldNumberInput: createSelector((name: unknown) => `data-testid form field-number-input-${name}`),
fieldColor: createSelector((name: unknown) => `data-testid form field-color-${name}`),
fieldInput: createSelector((name: unknown) => `data-testid form field-input-${name}`),
/**
* We should use default value for date-time-picker without data-testid prefix
* https://github.com/grafana/grafana/blob/f43762f39abad43f99b85cbcff6ca30c56f9d75f/packages/grafana-ui/src/components/DateTimePickers/DateTimePicker/DateTimePicker.tsx#L249
*/
fieldDatetime: () => `data-testid date-time-input`,
fieldRadio: (name: unknown) => `data-testid form field-radio-${name}`,
fieldDatetime: createSelector(() => `data-testid date-time-input`),
fieldRadio: createSelector((name: unknown) => `data-testid form field-radio-${name}`),
},
};
6 changes: 6 additions & 0 deletions packages/jest-selectors/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## 1.4.1 (2024-07-01)

### Features / Enhancements

- Add createSelector function (#49)

## 1.3.0 (2024-05-02)

### Features / Enhancements
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-selectors/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,5 @@
"typecheck": "tsc --emitDeclarationOnly false --noEmit"
},
"types": "dist/index.d.ts",
"version": "1.3.0"
"version": "1.4.1"
}
21 changes: 20 additions & 1 deletion packages/jest-selectors/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,28 @@ type JestSelector<TArgs extends unknown[]> = (
...args: TArgs
) => ReturnType<GetByBoundAttribute>;

/**
* Check If Selector Object
*/
type IsSelectorObject<TCandidate> = TCandidate extends {
selector: (...args: unknown[]) => void;
apply: (...args: unknown[]) => void;
}
? TCandidate & { selector: TCandidate['selector']; apply: TCandidate['apply'] }
: never;

/**
* Jest Selectors
*/
export type JestSelectors<T> = {
[K in keyof T]: T[K] extends (...args: infer Args) => void ? JestSelector<Args> : JestSelector<[]>;
[K in keyof T]: T[K] extends (...args: infer Args) => void
? JestSelector<Args>
: T[K] extends IsSelectorObject<T[K]>
? JestSelector<Parameters<T[K]['selector']>>
: JestSelector<[]>;
};

/**
* Selector Function
*/
export type SelectorFn = (...args: unknown[]) => string;
Loading

0 comments on commit 6838e64

Please sign in to comment.