diff --git a/apps/react-17-tests-v9/config/support/commands.js b/apps/react-17-tests-v9/config/support/commands.js new file mode 100644 index 0000000000000..045dab2b3d635 --- /dev/null +++ b/apps/react-17-tests-v9/config/support/commands.js @@ -0,0 +1,7 @@ +import { realPress } from 'cypress-real-events/commands/realPress'; + +/** + * Press command fallback for Cypress 13 compatibility. + * The press command is available in Cypress 14+ but not in Cypress 13. + */ +Cypress.Commands.add('press', realPress); diff --git a/apps/react-17-tests-v9/config/support/component.js b/apps/react-17-tests-v9/config/support/component.js new file mode 100644 index 0000000000000..b74bc56b30466 --- /dev/null +++ b/apps/react-17-tests-v9/config/support/component.js @@ -0,0 +1,5 @@ +// base config support file +import '../../../../scripts/cypress/src/support/component'; + +// custom commands +import './commands'; diff --git a/apps/react-17-tests-v9/cypress-react-17.config.ts b/apps/react-17-tests-v9/cypress-react-17.config.ts index dd61ebda49147..06c1796e59ba7 100644 --- a/apps/react-17-tests-v9/cypress-react-17.config.ts +++ b/apps/react-17-tests-v9/cypress-react-17.config.ts @@ -13,7 +13,13 @@ const specs = [ path.resolve('../../packages/react-components/react-tabster/src/**/*.cy.{tsx,ts}'), ...excludedSpecs, ]; -const config = { ...baseConfig }; +const config = { + ...baseConfig, + component: { + ...baseConfig.component, + supportFile: path.resolve(__dirname, 'config/support/component.js'), + }, +}; config.component.specPattern = specs; config.component.devServer.webpackConfig.resolve ??= {}; diff --git a/change/@fluentui-react-utilities-649e1127-b2c6-4503-861d-4c57f42bf7ad.json b/change/@fluentui-react-utilities-649e1127-b2c6-4503-861d-4c57f42bf7ad.json new file mode 100644 index 0000000000000..87af57f2b7f69 --- /dev/null +++ b/change/@fluentui-react-utilities-649e1127-b2c6-4503-861d-4c57f42bf7ad.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: enhance hook to support React concurrent mode using setEffect for first mount tracking", + "packageName": "@fluentui/react-utilities", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-drawer/library/src/components/DrawerFooter/DrawerFooter.cy.tsx b/packages/react-components/react-drawer/library/src/components/DrawerFooter/DrawerFooter.cy.tsx index 7ed078e81592a..fdef24f4b50c4 100644 --- a/packages/react-components/react-drawer/library/src/components/DrawerFooter/DrawerFooter.cy.tsx +++ b/packages/react-components/react-drawer/library/src/components/DrawerFooter/DrawerFooter.cy.tsx @@ -43,7 +43,7 @@ describe('DrawerFooter', () => { cy.get('#drawer-body').scrollTo('center'); cy.get('#drawer-footer').within($el => { - cy.window().then(win => { + cy.window().should(win => { const before = win.getComputedStyle($el[0], '::before'); const opacity = before.getPropertyValue('opacity'); diff --git a/packages/react-components/react-tabster/src/useKeyborgRef.cy.tsx b/packages/react-components/react-tabster/src/useKeyborgRef.cy.tsx index 13acbbda3d826..fe04dcc34f562 100644 --- a/packages/react-components/react-tabster/src/useKeyborgRef.cy.tsx +++ b/packages/react-components/react-tabster/src/useKeyborgRef.cy.tsx @@ -23,12 +23,12 @@ describe('Keyborg', () => { it('should dispose keyborg instance on unmount', () => { mount(); - cy.window().then(win => { + cy.window().should(win => { // @ts-expect-error - Only way to definitively check if keyborg is disposed expect(win.__keyborg).not.equals(undefined); }); mount(
Unmounted
); - cy.window().then(win => { + cy.window().should(win => { // @ts-expect-error - Only way to definitively check if keyborg is disposed expect(win.__keyborg).equals(undefined); }); diff --git a/packages/react-components/react-tree/library/src/components/FlatTree/FlatTree.cy.tsx b/packages/react-components/react-tree/library/src/components/FlatTree/FlatTree.cy.tsx index a20bcacb14af9..3b08192ba9fb5 100644 --- a/packages/react-components/react-tree/library/src/components/FlatTree/FlatTree.cy.tsx +++ b/packages/react-components/react-tree/library/src/components/FlatTree/FlatTree.cy.tsx @@ -203,9 +203,9 @@ describe('FlatTree', () => { , ); cy.focused().should('not.exist'); - cy.document().realPress('Tab'); + cy.document().press('Tab'); cy.get('[data-testid="item1"]').should('be.focused'); - cy.document().realPress('Tab'); + cy.document().press('Tab'); cy.get('#action').should('be.focused'); }); describe('navigationMode="treegrid"', () => { @@ -241,9 +241,9 @@ describe('FlatTree', () => { , ); cy.focused().should('not.exist'); - cy.document().realPress('Tab'); + cy.document().press('Tab'); cy.get('[data-testid="item1"]').should('be.focused'); - cy.document().realPress('Tab'); + cy.document().press('Tab'); cy.get('#action').should('be.focused').realPress('{enter}'); cy.get('[data-testid="item1__item1"]').should('not.exist'); cy.get('#action').should('be.focused').realPress('Space'); @@ -252,15 +252,15 @@ describe('FlatTree', () => { it('should focus on first item when pressing tab key', () => { mount(); cy.focused().should('not.exist'); - cy.document().realPress('Tab'); + cy.document().press('Tab'); cy.get('[data-testid="item1"]').should('be.focused'); }); it('should focus out of tree when pressing tab key inside tree.', () => { mount(); cy.focused().should('not.exist'); - cy.document().realPress('Tab'); + cy.document().press('Tab'); cy.get('[data-testid="item1"]').should('be.focused'); - cy.focused().realPress('Tab'); + cy.focused().press('Tab'); cy.focused().should('not.exist'); }); describe('Navigation', () => { @@ -268,7 +268,7 @@ describe('FlatTree', () => { mount(); cy.get('[data-testid="item1"]').focus().realPress('{downarrow}'); cy.get('[data-testid="item2"]').should('be.focused'); - cy.focused().realPress('Tab').should('not.exist'); + cy.focused().press('Tab').should('not.exist'); }); describe('navigationMode="treegrid"', () => { it('should move with Up/Down keys', () => { @@ -434,7 +434,7 @@ describe('FlatTree', () => { defaultCheckedItems={['item1__item1']} />, ); - cy.window().then(win => { + cy.window().should(win => { expect(win.console.error).to.be.called; }); }); diff --git a/packages/react-components/react-utilities/src/hooks/useFirstMount.ts b/packages/react-components/react-utilities/src/hooks/useFirstMount.ts index 0c046529894d5..68a3c05fd2a62 100644 --- a/packages/react-components/react-utilities/src/hooks/useFirstMount.ts +++ b/packages/react-components/react-utilities/src/hooks/useFirstMount.ts @@ -3,10 +3,8 @@ import * as React from 'react'; /** * @internal * Checks if components was mounted the first time. - * Since concurrent mode will be released in the future this needs to be verified - * Currently (React 17) will always render the initial mount once - * https://codesandbox.io/s/heuristic-brook-s4w0q?file=/src/App.jsx - * https://codesandbox.io/s/holy-grass-8nieu?file=/src/App.jsx + * Supports React concurrent/strict mode by using `useEffect` + * to track the first mount instead of mutating refs during render. * * @example * const isFirstMount = useFirstMount(); @@ -14,10 +12,11 @@ import * as React from 'react'; export function useFirstMount(): boolean { const isFirst = React.useRef(true); - if (isFirst.current) { - isFirst.current = false; - return true; - } + React.useEffect(() => { + if (isFirst.current) { + isFirst.current = false; + } + }, []); return isFirst.current; }