Skip to content

Commit

Permalink
feat: setup and fix rules from eslint-plugin-react-hooks (#1025)
Browse files Browse the repository at this point in the history
  • Loading branch information
quentinderoubaix authored Nov 20, 2024
1 parent a7fc146 commit de19c38
Show file tree
Hide file tree
Showing 23 changed files with 145 additions and 105 deletions.
6 changes: 4 additions & 2 deletions e2e/demo.demo-e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,21 @@ test.describe.parallel('Demo Website', () => {
const frames = page.frames();
if (frames.length > 1) {
const iframes = frames.slice(1);
// eslint-disable-next-line playwright/no-conditional-expect
await Promise.all(iframes.map((frame) => expect.poll(() => frame.url()).not.toBe('about:blank')));
await Promise.all(iframes.map((frame) => frame.waitForURL(frame.url())));
}
expect((await analyze(page, route)).violations).toEqual([]);
expect((await analyze(page)).violations).toEqual([]);

Check failure on line 52 in e2e/demo.demo-e2e-spec.ts

View workflow job for this annotation

GitHub Actions / e2e-tests / Test (8/10)

[demo:chromium] › demo.demo-e2e-spec.ts:43:3 › Demo Website › Route docs/svelte/components/collapse/api should be accessible

1) [demo:chromium] › demo.demo-e2e-spec.ts:43:3 › Demo Website › Route docs/svelte/components/collapse/api should be accessible Error: expect(received).toEqual(expected) // deep equality - Expected - 1 + Received + 185 - Array [] + Array [ + Object { + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/color-contrast?application=playwright", + "id": "color-contrast", + "impact": "serious", + "nodes": Array [ + Object { + "all": Array [], + "any": Array [ + Object { + "data": Object { + "bgColor": "#eff5fd", + "contrastRatio": 1.98, + "expectedContrastRatio": "4.5:1", + "fgColor": "#a1b1c7", + "fontSize": "12.0pt (16px)", + "fontWeight": "normal", + "messageKey": null, + }, + "id": "color-contrast", + "impact": "serious", + "message": "Element has insufficient color contrast of 1.98 (foreground color: #a1b1c7, background color: #eff5fd, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1", + "relatedNodes": Array [ + Object { + "html": "<div role=\"alert\" class=\"au-alert alert alert-info p-0 border-0 border-start border-5 border-info alert-dismissible fade show\">", + "target": Array [ + ".alert-dismissible", + ], + }, + Object { + "html": "<body data-sveltekit-preload-data=\"hover\">", + "target": Array [ + "body", + ], + }, + ], + }, + ], + "failureSummary": "Fix any of the following: + Element has insufficient color contrast of 1.98 (foreground color: #a1b1c7, background color: #eff5fd, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1", + "html": "<div class=\"alert-container p-3 border border-info rounded-end\">", + "impact": "serious", + "none": Array [], + "target": Array [ + ".alert-dismissible > .alert-body > .alert-container.p-3.border", + ], + }, + Object { + "all": Array [], + "any": Array [ + Object { + "data": Object { + "bgColor": "#eff5fd", + "contrastRatio": 1.98, + "expectedContrastRatio": "4.5:1", + "fgColor": "#a1b1c7", + "fontSize": "12.0pt (16px)", + "fontWeight": "normal", + "messageKey": null, + }, + "id": "color-contrast", + "impact": "serious", + "message": "Element has insufficient color contrast of 1.98 (foreground color: #a1b1c7, background color: #eff5fd, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1", + "relatedNodes": Array [ + Object { + "html": "<div role=\"alert\" class=\"au-alert alert alert-info p-0 border-0 border-start border-5 border-info alert-dismissible fade show\">", + "target": Array [ + ".alert-dismissible", + ], + }, + Object { + "html": "<body data-sveltekit-preload-data=\"hover\">", + "target": Array [ + "body", + ], + }, + ], + },

Check failure on line 52 in e2e/demo.demo-e2e-spec.ts

View workflow job for this annotation

GitHub Actions / e2e-tests / Test (8/10)

[demo:chromium] › demo.demo-e2e-spec.ts:43:3 › Demo Website › Route docs/svelte/components/select/api should be accessible

2) [demo:chromium] › demo.demo-e2e-spec.ts:43:3 › Demo Website › Route docs/svelte/components/select/api should be accessible Error: expect(received).toEqual(expected) // deep equality - Expected - 1 + Received + 349 - Array [] + Array [ + Object { + "description": "Ensure the contrast between foreground and background colors meets WCAG 2 AA minimum contrast ratio thresholds", + "help": "Elements must meet minimum color contrast ratio thresholds", + "helpUrl": "https://dequeuniversity.com/rules/axe/4.10/color-contrast?application=playwright", + "id": "color-contrast", + "impact": "serious", + "nodes": Array [ + Object { + "all": Array [], + "any": Array [ + Object { + "data": Object { + "bgColor": "#fffae9", + "contrastRatio": 2.13, + "expectedContrastRatio": "4.5:1", + "fgColor": "#bbad83", + "fontSize": "12.0pt (16px)", + "fontWeight": "normal", + "messageKey": null, + }, + "id": "color-contrast", + "impact": "serious", + "message": "Element has insufficient color contrast of 2.13 (foreground color: #bbad83, background color: #fffae9, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1", + "relatedNodes": Array [ + Object { + "html": "<div role=\"alert\" class=\"au-alert alert alert-warning p-0 border-0 border-start border-5 border-warning alert-dismissible fade show\">", + "target": Array [ + ".alert-warning", + ], + }, + Object { + "html": "<body data-sveltekit-preload-data=\"hover\">", + "target": Array [ + "body", + ], + }, + ], + }, + ], + "failureSummary": "Fix any of the following: + Element has insufficient color contrast of 2.13 (foreground color: #bbad83, background color: #fffae9, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1", + "html": "<div class=\"alert-container p-3 border border-warning rounded-end\">", + "impact": "serious", + "none": Array [], + "target": Array [ + ".border-warning.alert-container.p-3", + ], + }, + Object { + "all": Array [], + "any": Array [ + Object { + "data": Object { + "bgColor": "#fffae9", + "contrastRatio": 2.13, + "expectedContrastRatio": "4.5:1", + "fgColor": "#bbad83", + "fontSize": "12.0pt (16px)", + "fontWeight": "normal", + "messageKey": null, + }, + "id": "color-contrast", + "impact": "serious", + "message": "Element has insufficient color contrast of 2.13 (foreground color: #bbad83, background color: #fffae9, font size: 12.0pt (16px), font weight: normal). Expected contrast ratio of 4.5:1", + "relatedNodes": Array [ + Object { + "html": "<div role=\"alert\" class=\"au-alert alert alert-warning p-0 border-0 border-start border-5 border-warning alert-dismissible fade show\">", + "target": Array [ + ".alert-warning", + ], + }, + Object { + "html": "<body data-sveltekit-preload-data=\"hover\">", + "target": Array [ + "body", + ], + }, + ], + }, + ],
await page.evaluate(() => document.documentElement.setAttribute('data-bs-theme', 'dark'));
await page.locator('.btn-dark-mode').first().click();
await page.locator('.dropdown-menu button:has-text("Dark")').click();
expect((await analyze(page, route)).violations).toEqual([]);
expect((await analyze(page)).violations).toEqual([]);
});
}
});

test.describe('Sitemap', () => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(process.env.CI !== 'true', 'sitemap tests for CI only');

test(`sitemap.xml should contain the blog and framework introduction pages`, async ({page}) => {
Expand Down
1 change: 1 addition & 0 deletions e2e/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const test = base.extend<FixtureOptions>({
},
baseURL: [
async ({project, framework, sampleKey, sampleInfo}, use) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(project === 'stackblitz' && sampleInfo?.sampleName === 'playground', 'Playground samples are not supported in stackblitz');
test.fixme(
sampleKey === 'bootstrap/slots/usage',
Expand Down
1 change: 1 addition & 0 deletions e2e/focustrack/focustrack.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ test.describe(`Select tests`, () => {
expectedState.activeElements.push('input with id otherFocusableInput');
await expect.poll(() => focustrackPO.getState()).toEqual(expectedState);

// eslint-disable-next-line playwright/no-force-option
await focustrackPO.locatorDisabledInput.click({force: true});

expectedState.activeElements.push('body');
Expand Down
2 changes: 2 additions & 0 deletions e2e/rating/rating.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ test.describe.parallel(`Rating tests`, () => {
};
await expect.poll(() => ratingPO.state()).toEqual(expectedState);

// eslint-disable-next-line playwright/no-force-option
await ratingPO.locatorStar(4).click({force: true});
await expect.poll(() => ratingPO.state()).toEqual(expectedState);
});
Expand Down Expand Up @@ -159,6 +160,7 @@ test.describe.parallel(`Rating tests`, () => {
await expect.poll(() => ratingDemoPO.formRatingDemoState()).toEqual(expectedState);

await ratingDemoPO.locatorBtnEnabled.click();
// eslint-disable-next-line playwright/no-force-option
await ratingPO.locatorStar(2).click({force: true});
expectedState = {
...expectedState,
Expand Down
1 change: 1 addition & 0 deletions e2e/samplesMarkup.singlebrowser-e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ test.describe(`Samples markup consistency check`, () => {
for (const sampleKey of samples) {
test.describe(`Sample ${sampleKey}`, () => {
test.use({sampleKey});
// eslint-disable-next-line playwright/no-skipped-test
test.skip(({sampleInfo}) => !sampleInfo, `The sample cannot be tested in this configuration`);

test(`should have a consistent markup`, async ({page, baseURL}) => {
Expand Down
1 change: 1 addition & 0 deletions e2e/slider/slider.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ test.describe(`Slider tests`, () => {
const sliderProgressLocator = sliderPO.locatorProgress;
const boundingBox = await sliderProgressLocator.boundingBox();
// force the click though the progress bar
// eslint-disable-next-line playwright/no-force-option
await sliderProgressLocator.click({position: {x: boundingBox!.x + boundingBox!.width * 0.5, y: 1}, force: true});

await expect
Expand Down
2 changes: 2 additions & 0 deletions e2e/transition/transition.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ test.describe.parallel('Transition tests', () => {
if (!scenario.removingDomElement) {
await po.locatorRemoveFromDOMCheckbox.click();
expectedState.removeFromDOM = false;
// eslint-disable-next-line playwright/no-conditional-expect
await expect.poll(() => po.getState()).toEqual(expectedState);
}

Expand Down Expand Up @@ -120,6 +121,7 @@ test.describe.parallel('Transition tests', () => {
if (!scenario.removingDomElement) {
await po.locatorRemoveFromDOMCheckbox.click();
expectedState.removeFromDOM = false;
// eslint-disable-next-line playwright/no-conditional-expect
await expect.poll(() => po.getState()).toEqual(expectedState);
}

Expand Down
12 changes: 12 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import jsdoc from 'eslint-plugin-jsdoc';
import react from 'eslint-plugin-react';
import svelteParser from 'svelte-eslint-parser';
import angular from 'angular-eslint';
import reactHooks from 'eslint-plugin-react-hooks';

export default tseslint.config(
// global ignores
Expand Down Expand Up @@ -49,6 +50,10 @@ export default tseslint.config(
{
...playwright.configs['flat/recommended'],
files: ['e2e/**'],
rules: {
...playwright.configs['flat/recommended'].rules,
'playwright/no-conditional-in-test': 'off',
},
},
{
files: ['**/*.spec.ts'],
Expand Down Expand Up @@ -243,6 +248,13 @@ export default tseslint.config(
...react.configs.flat['jsx-runtime'],
files: ['react/**/*.{tsx,jsx}'],
},
{
plugins: {
['react-hooks']: reactHooks,
},
rules: reactHooks.configs.recommended.rules,
files: ['react/**/*.{tsx,jsx}'],
},
{
files: ['react/headless/src/**'],
rules: {
Expand Down
13 changes: 13 additions & 0 deletions package-lock.json

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

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"eslint-plugin-jsdoc": "^50.5.0",
"eslint-plugin-playwright": "^2.1.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-svelte": "^2.46.0",
"express": "^4.21.1",
"glob": "11.0.0",
Expand All @@ -78,9 +79,6 @@
"optionalDependencies": {
"@esbuild/darwin-arm64": "0.24.0",
"@lmdb/lmdb-win32-x64": "3.1.6",
"@rollup/rollup-darwin-arm64": "4.27.3",
"@rollup/rollup-darwin-x64": "4.27.3",
"@rollup/rollup-win32-x64-msvc": "4.27.3",
"@parcel/watcher-android-arm64": "2.5.0",
"@parcel/watcher-darwin-arm64": "2.5.0",
"@parcel/watcher-darwin-x64": "2.5.0",
Expand All @@ -92,7 +90,10 @@
"@parcel/watcher-linux-x64-musl": "2.5.0",
"@parcel/watcher-win32-arm64": "2.5.0",
"@parcel/watcher-win32-ia32": "2.5.0",
"@parcel/watcher-win32-x64": "2.5.0"
"@parcel/watcher-win32-x64": "2.5.0",
"@rollup/rollup-darwin-arm64": "4.27.3",
"@rollup/rollup-darwin-x64": "4.27.3",
"@rollup/rollup-win32-x64-msvc": "4.27.3"
},
"scripts": {
"build:coverage": "wireit",
Expand Down
6 changes: 3 additions & 3 deletions react/bootstrap/src/components/accordion/accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ export const AccordionItem: ForwardRefExoticComponent<Partial<AccordionItemProps
structure: AccordionItemDefaultSlotStructure,
});
const {state, api, directives} = widgetContext;
useImperativeHandle(ref, () => api, []);
useImperativeHandle(ref, () => api, [api]);
useEffect(() => {
api.initDone();
}, []);
}, [api]);
return (
<div {...useDirectives([classDirective, `accordion-item ${state.className}`], directives.itemDirective)}>
<Slot slotContent={state.structure} props={widgetContext} />
Expand All @@ -95,7 +95,7 @@ export const AccordionItem: ForwardRefExoticComponent<Partial<AccordionItemProps
export const Accordion: ForwardRefExoticComponent<PropsWithChildren<Partial<AccordionProps>> & RefAttributes<AccordionApi>> = forwardRef(
function Accordion(props: PropsWithChildren<Partial<AccordionProps>>, ref: ForwardedRef<AccordionApi>) {
const widget = useWidgetWithConfig(createAccordion, props, 'accordion');
useImperativeHandle(ref, () => widget.api, []);
useImperativeHandle(ref, () => widget.api, [widget.api]);
return (
<AccordionDIContext.Provider value={widget.api}>
<div {...useDirectives([classDirective, 'accordion'], widget.directives.accordionDirective)}>{props.children}</div>
Expand Down
2 changes: 1 addition & 1 deletion react/bootstrap/src/components/alert/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ export const Alert: ForwardRefExoticComponent<Partial<AlertProps> & RefAttribute
const widgetContext = useWidgetWithConfig(createAlert, props, 'alert', {
structure: AlertDefaultSlotStructure,
});
useImperativeHandle(ref, () => widgetContext.api, []);
useImperativeHandle(ref, () => widgetContext.api, [widgetContext.api]);
return <>{!widgetContext.state.hidden && <AlertElement {...widgetContext} />}</>;
});
2 changes: 1 addition & 1 deletion react/bootstrap/src/components/collapse/collapse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {useDirectives} from '@agnos-ui/react-headless/utils/directive';
export const Collapse: ForwardRefExoticComponent<PropsWithChildren<Partial<CollapseProps>> & RefAttributes<CollapseApi>> = forwardRef(
function Collapse(props: PropsWithChildren<Partial<CollapseProps>>, ref: ForwardedRef<CollapseApi>) {
const {api, directives} = useWidgetWithConfig(createCollapse, props, 'collapse');
useImperativeHandle(ref, () => api, []);
useImperativeHandle(ref, () => api, [api]);

return <div {...useDirectives(directives.collapseDirective)}>{props.children}</div>;
},
Expand Down
2 changes: 1 addition & 1 deletion react/bootstrap/src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const Modal = forwardRef(function Modal<Data>(props: Partial<ModalProps<D
header: ModalDefaultSlotHeader,
structure: ModalDefaultSlotStructure,
});
useImperativeHandle(ref, () => widgetContext.api, []);
useImperativeHandle(ref, () => widgetContext.api, [widgetContext.api]);
return (
<Portal container={widgetContext.state.container}>
{!widgetContext.state.backdropHidden && <BackdropElement {...widgetContext} />}
Expand Down
10 changes: 6 additions & 4 deletions react/bootstrap/src/components/toast/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import {useWidgetWithConfig} from '../../config';
import type {ToastApi, ToastContext, ToastProps} from './toast.gen';
import {createToast} from './toast.gen';

const ToastHeaderContent = (slotContext: ToastContext) => (
<button {...useDirectives([classDirective, 'btn-close me-0 ms-auto'], slotContext.directives.closeButtonDirective)} />
);

const ToastHeader = (slotContext: ToastContext) => (
<div className="toast-header">
<Slot slotContent={slotContext.state.header} props={slotContext} />
{slotContext.state.dismissible && (
<button {...useDirectives([classDirective, 'btn-close me-0 ms-auto'], slotContext.directives.closeButtonDirective)} />
)}
{slotContext.state.dismissible && <ToastHeaderContent {...slotContext} />}
</div>
);

Expand Down Expand Up @@ -69,7 +71,7 @@ export const Toast: ForwardRefExoticComponent<Partial<ToastProps> & RefAttribute
structure: ToastDefaultSlotStructure,
children: props.children,
});
useImperativeHandle(ref, () => widgetContext.api, []);
useImperativeHandle(ref, () => widgetContext.api, [widgetContext.api]);

return <>{!widgetContext.state.hidden && <ToastElement {...widgetContext} />}</>;
});
Loading

0 comments on commit de19c38

Please sign in to comment.