Skip to content

Commit

Permalink
test(components-react): add visual baseline to all formfield componen…
Browse files Browse the repository at this point in the history
…ts (#347)

# Contents

Add visual tests baseline to all formfield components


Co-authored-by: Jaap-Hein Wester <[email protected]>
Co-authored-by: Remy Parzinski <[email protected]>
  • Loading branch information
3 people authored Jan 3, 2025
1 parent 2f40d5d commit bfde11c
Show file tree
Hide file tree
Showing 29 changed files with 690 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
outline: none;
}

&:not(#{&}--disabled):has(.utrecht-form-field__input .utrecht-checkbox:hover)::before {
&--with-target:not(#{&}--disabled):has(.utrecht-form-field__input .utrecht-checkbox:hover)::before {
border-color: var(--lux-form-field-checkbox-hover-inner-border-color);
background-color: var(--lux-form-field-checkbox-hover-inner-background-color);
color: var(--lux-form-field-checkbox-hover-inner-color);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import clsx from 'clsx';
import { useId } from 'react';
import { LuxCheckbox } from '../checkbox/Checkbox';
import { LuxFormField, LuxFormFieldProps } from '../form-field/FormField';
import { LuxCheckbox, type LuxCheckboxProps } from '../checkbox/Checkbox';
import { LuxFormField, type LuxFormFieldProps } from '../form-field/FormField';
import {
LuxFormFieldDescription,
type LuxFormFieldDescriptionAppearance,
} from '../form-field-description/FormFieldDescription';
import { LuxFormFieldErrorMessage } from '../form-field-error-message/FormFieldErrorMessage';
import { LuxFormFieldLabel } from '../form-field-label/FormFieldLabel';
import './FormFieldCheckbox.scss';
import { pick } from '../utils/object';

export type LuxFormFieldCheckboxProps = LuxFormFieldProps & {
checked?: boolean;
disabled?: boolean;
appearance?: LuxFormFieldDescriptionAppearance;
withTarget?: boolean;
distanced?: boolean;
};
export type LuxFormFieldCheckboxProps = LuxFormFieldProps &
LuxCheckboxProps & {
checked?: boolean;
disabled?: boolean;
appearance?: LuxFormFieldDescriptionAppearance;
withTarget?: boolean;
distanced?: boolean;
};

export const LuxFormFieldCheckbox = ({
label,
Expand Down Expand Up @@ -73,6 +75,17 @@ export const LuxFormFieldCheckbox = ({
errorMessage
);

const checkBoxAttrs = pick(restProps, [
'required',
'inputRequired',
'value',
'defaultValue',
'onFocus',
'onBlur',
'onInput',
'onChange',
]);

return (
<LuxFormField
type="checkbox"
Expand All @@ -81,7 +94,14 @@ export const LuxFormFieldCheckbox = ({
errorMessage={errorMessageNode}
invalid={invalid}
input={
<LuxCheckbox id={inputId} disabled={disabled} invalid={invalid} checked={checked} withTarget={withTarget} />
<LuxCheckbox
id={inputId}
disabled={disabled}
invalid={invalid}
checked={checked}
withTarget={withTarget}
{...checkBoxAttrs}
/>
}
className={clsx('lux-form-field-checkbox', {
'lux-form-field-checkbox--invalid': invalid,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, expect, it } from '@jest/globals';
import { render, screen } from '@testing-library/react';
import { LuxFormFieldCheckbox } from '../FormFieldCheckbox';

describe('Form Field Checkbox', () => {
it('renders a basic form field checkbox with label and input', () => {
render(<LuxFormFieldCheckbox label="Name" />);

expect(screen.getByText('Name')).toBeInTheDocument();
expect(screen.getByRole('checkbox')).toBeInTheDocument();
});

it('applies the base class', () => {
render(<LuxFormFieldCheckbox label="Name" />);

const formField = screen.getByText('Name').closest('.utrecht-form-field');
expect(formField).toHaveClass('utrecht-form-field');
});

it('can have an additional class name', () => {
render(<LuxFormFieldCheckbox label="Name" className="custom-class" />);

const formField = screen.getByText('Name').closest('.utrecht-form-field');
expect(formField).toHaveClass('utrecht-form-field');
expect(formField).toHaveClass('custom-class');
});

it('renders description when provided', () => {
render(<LuxFormFieldCheckbox label="Name" description="Enter your full name" />);

expect(screen.getByText('Enter your full name')).toBeInTheDocument();
});

it('renders error message when invalid and error message provided', () => {
render(<LuxFormFieldCheckbox label="Name" invalid={true} errorMessage="Name is required" />);

expect(screen.getByText('Name is required')).toBeInTheDocument();
});

it('adds the correct attributes to the Checkbox', () => {
render(<LuxFormFieldCheckbox label="Name" checked required />);

const checkbox = screen.getByRole('checkbox');

expect(checkbox).toBeInTheDocument();
expect(checkbox).toBeChecked();
expect(checkbox).toHaveAttribute('aria-required', 'true');
expect(checkbox).not.toHaveAttribute('required');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import React, { ForwardedRef, forwardRef } from 'react';
import './FormFieldRadioGroup.css';
import { LuxFormFieldRadioOption } from '../form-field-radio-option/FormFieldRadioOption';

type RadioOptionValue = string | number;

interface RadioOption {
value: string;
value: RadioOptionValue;
label: string;
disabled?: boolean;
description?: React.ReactNode;
Expand All @@ -16,7 +18,7 @@ export interface LuxFormFieldRadioGroupProps {
description?: string;
errorMessage?: string;
options: RadioOption[];
value?: string;
value?: RadioOptionValue;
invalid?: boolean;
required?: boolean;
className?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
} from '../form-field-description/FormFieldDescription';
import { LuxFormFieldErrorMessage } from '../form-field-error-message/FormFieldErrorMessage';
import { LuxFormFieldLabel } from '../form-field-label/FormFieldLabel';
import { LuxSelect, type LuxSelectProps } from '../select/Select';
import { LuxSelect, LuxSelectOption, type LuxSelectOptionProps, type LuxSelectProps } from '../select/Select';

export type LuxFormFieldSelectOptionsProps = LuxSelectOptionProps & {
label?: string | number;
};

export interface LuxFormFieldSelectProps
extends Omit<UtrechtFormFieldProps, 'type' | 'onBlur' | 'onChange' | 'onFocus'>,
Expand All @@ -27,13 +31,15 @@ export interface LuxFormFieldSelectProps
| 'size'
| 'value'
> {
options?: LuxFormFieldSelectOptionsProps[];
appearance?: LuxFormFieldDescriptionAppearance;
distanced?: boolean;
inputRef?: Ref<HTMLSelectElement>;
}

export const LuxFormFieldSelect = ({
label,
options,
description,
errorMessage,
disabled,
Expand Down Expand Up @@ -114,10 +120,17 @@ export const LuxFormFieldSelect = ({
}) || undefined
}
{...selectAttrs}
/>
>
{options
? options.map((option) => (
<LuxSelectOption {...option} key={option.value}>
{option.label}
</LuxSelectOption>
))
: formFieldAttrs['children']}
</LuxSelect>
}
className={className}
{...formFieldAttrs}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { LuxFormFieldErrorMessage } from '../form-field-error-message/FormFieldErrorMessage';
import { LuxFormFieldLabel } from '../form-field-label/FormFieldLabel';
import { type Direction, LuxTextbox } from '../textbox/Textbox';
import { pick } from '../utils/object';

export type LuxFormFieldTextboxProps = UtrechtFormFieldTextboxProps & {
appearance?: LuxFormFieldDescriptionAppearance;
Expand Down Expand Up @@ -60,15 +61,6 @@ export const LuxFormFieldTextbox = ({
errorMessage
);

// TODO: naar utils
function pick<T extends object, U extends keyof T>(obj: T, paths: Array<U>): Pick<T, U> {
const ret = {} as Pick<T, U>;
for (const k of paths) {
ret[k] = obj[k];
}
return ret;
}

const textBoxAttrs = pick(restProps, [
'autoComplete',
'min',
Expand Down
6 changes: 5 additions & 1 deletion packages/components-react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ export {
type LuxFormFieldErrorMessageProps,
} from './form-field-error-message/FormFieldErrorMessage';
export { LuxFormFieldLabel, type LuxFormFieldLabelProps } from './form-field-label/FormFieldLabel';
export { LuxFormFieldSelect, type LuxFormFieldSelectProps } from './form-field-select/FormFieldSelect';
export {
LuxFormFieldSelect,
type LuxFormFieldSelectProps,
type LuxFormFieldSelectOptionsProps,
} from './form-field-select/FormFieldSelect';
export { LuxFormFieldTextbox, type LuxFormFieldTextboxProps } from './form-field-textbox/FormFieldTextbox';
export { LuxParagraph, type LuxParagraphProps } from './paragraph/Paragraph';
export {
Expand Down
7 changes: 4 additions & 3 deletions packages/components-react/src/select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import {
} from '@utrecht/component-library-react/dist/css-module';
import './Select.css';
import clsx from 'clsx';
import { forwardRef } from 'react';
import { ForwardedRef, forwardRef } from 'react';

export type LuxSelectProps = UtrechtSelectProps;
export type LuxSelectOptionProps = UtrechtSelectOptionProps;

export const LuxSelect = forwardRef((props: LuxSelectProps) => {
export const LuxSelect = forwardRef((props: LuxSelectProps, ref: ForwardedRef<HTMLSelectElement>) => {
const { className, ...restProps } = props;

return <UtrechtSelect className={clsx(className, 'lux-select')} {...restProps} />;
return <UtrechtSelect ref={ref} className={clsx(className, 'lux-select')} {...restProps} />;
});

export const LuxSelectOption = SelectOption;
Expand Down
17 changes: 17 additions & 0 deletions packages/components-react/src/utils/object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Pick only certain keys form object.
*
* @export
* @template {object} T
* @template {keyof T} U
* @param {T} obj Object to pick from
* @param {Array<U>} keys Keys to pick
* @returns {Pick<T, U>} Object containing only picked keys
*/
export function pick<T extends object, U extends keyof T>(obj: T, keys: Array<U>): Pick<T, U> {
const ret = {} as Pick<T, U>;
for (const k of keys) {
ret[k] = obj[k];
}
return ret;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { LuxFormFieldCheckbox, type LuxFormFieldCheckboxProps } from '@lux-design-system/components-react';
import tokens from '@lux-design-system/design-tokens/dist/index.json';
import type { Meta, StoryObj } from '@storybook/react';
import { VisualStates } from './visual/States';
import { BADGES } from '../../../config/preview';
import { createDesignTokensStory, createVisualRegressionStory, VisualRegressionWrapper } from '../../utils';
import CheckboxMeta from '../checkbox/checkbox.stories';
import FormFieldDescriptionMeta from '../form-field-description/form-field-description.stories';
import FormFieldErrorMessageMeta from '../form-field-error-message/form-field-error-message.stories';
Expand Down Expand Up @@ -91,6 +93,14 @@ export const WithTarget: Story = {
...Playground.args,
withTarget: true,
},
parameters: {
docs: {
description: {
story:
'Met `withTarget` wordt het hele component (behalve de foutmelding) een klikdoel. _Let op:_ dit kan voor gebruikers onverwacht zijn.',
},
},
},
};

export const withLongTexts: Story = {
Expand All @@ -105,3 +115,18 @@ export const withLongTexts: Story = {
withTarget: true,
},
};

export const DesignTokens = createDesignTokensStory(meta);

export const Visual = createVisualRegressionStory(() => (
<>
<h4 className="utrecht-heading-3">Light</h4>
<VisualRegressionWrapper className={`lux-theme--logius-light`}>
<VisualStates />
</VisualRegressionWrapper>
<h4 className="utrecht-heading-3">Dark</h4>
<VisualRegressionWrapper className={`lux-theme--logius-dark`}>
<VisualStates />
</VisualRegressionWrapper>
</>
));
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { LuxFormFieldCheckbox } from '@lux-design-system/components-react';

export const VisualStates = () => (
<>
<LuxFormFieldCheckbox label="Label" />
<LuxFormFieldCheckbox label="Label" checked />
<LuxFormFieldCheckbox label="Label" description="Description" />
<h5 className="utrecht-heading-4">Hover &amp; Focus</h5>
<div className="pseudo-hover-all">
<LuxFormFieldCheckbox label="Label" />
</div>
<div className="pseudo-focus-all pseudo-focus-visible-all">
<LuxFormFieldCheckbox label="Label" />
</div>
<h5 className="utrecht-heading-4">Invalid</h5>
<LuxFormFieldCheckbox label="Label" errorMessage="Error Message" invalid />
<LuxFormFieldCheckbox label="Label" description="Description" errorMessage="Error Message" invalid />
<h5 className="utrecht-heading-4">Disabled</h5>
<LuxFormFieldCheckbox label="Label" disabled />
<LuxFormFieldCheckbox label="Label" disabled checked />
<LuxFormFieldCheckbox label="Label" description="Description" disabled />
</>
);
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ import { CitationDocumentation } from "../../utils/CitationDocumentation";
<Description of={FormFieldDescriptionStories.Invalid} />
<Canvas of={FormFieldDescriptionStories.Invalid} />

### Appearance vormgegeven met tokens

<Description of={FormFieldDescriptionStories.CustomAppearance} />
<Canvas of={FormFieldDescriptionStories.CustomAppearance} sourceState="none" />

### Lange Beschrijving

<Description of={FormFieldDescriptionStories.LongDescription} />
Expand Down
Loading

0 comments on commit bfde11c

Please sign in to comment.