Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FocusTrapZone: port most tests to cypress #21741

Merged
merged 11 commits into from
Mar 2, 2022

Conversation

ecraig12345
Copy link
Member

@ecraig12345 ecraig12345 commented Feb 14, 2022

Current Behavior

FocusTrapZone's tests do so much mocking that it's not clear whether they're actually validating the things they claim to be validating. Also, they use enzyme (which we're trying to get rid of) and some of the mocking stops working when upgrading to jest 27.

New Behavior

Move most of the tests to use Cypress. In all cases I'm testing the same things as in the removed jest tests with the same descriptions, but I changed the element IDs to be more semantic to try and make it easier to follow what the tests are doing, and removed some data-* props that don't seem to be needed in the actual browser. The layouts are also basically the same as they were with the manually-set clientRect mocks, just sometimes achieved with different styling (and larger button sizes to accommodate the text).

One pattern that I'm currently using in this PR but don't really like is creating global functions on window for a few cases. If anyone has better suggestions for how to approach any of these, I'd appreciate it.

  • reusing the same story for different tests, with slightly different props
    • could potentially integrate @cypress/react which allows direct rendering of React components within the Cypress test, but that would probably be done in a separate PR
  • testing the imperative focus() method
  • verifying that the current FocusTrapZone.focusStack is correct (I'm not sure if there's an alternative way to test this based on the rendered DOM)
  • manually showing/hiding more FocusTrapZones during the focus stack test (this might be possible by rendering and clicking a series of buttons, which is sort of what the example did before, but each button would have to be in the currently active FTZ)

Another thing to consider for the future is whether we want to add @testing-library/cypress to facilitate testing that's more like actual user interaction patterns, similar to @testing-library/react. That would probably be done in a separate PR.

Related Issue(s)

Part of #21663

@codesandbox-ci
Copy link

codesandbox-ci bot commented Feb 14, 2022

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 9ff5d82:

Sandbox Source
@fluentui/react 8 starter Configuration
@fluentui/react-components 9 starter Configuration

@size-auditor
Copy link

size-auditor bot commented Feb 14, 2022

Asset size changes

Size Auditor did not detect a change in bundle size for any component!

Baseline commit: d4c33467e224fee30434372238eedf5e7f7d3866 (build)

@fabricteam
Copy link
Collaborator

fabricteam commented Feb 14, 2022

📊 Bundle size report

Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-accordion
Accordion (including children components)
72.822 kB
22.054 kB
react-avatar
Avatar
44.943 kB
13.043 kB
react-badge
Badge
20.869 kB
6.549 kB
react-badge
CounterBadge
21.737 kB
6.843 kB
react-badge
PresenceBadge
21.827 kB
6.519 kB
react-button
Button
27.982 kB
8.043 kB
react-button
CompoundButton
33.246 kB
9.007 kB
react-button
MenuButton
29.65 kB
8.624 kB
react-button
SplitButton
36.132 kB
9.835 kB
react-button
ToggleButton
37.28 kB
8.647 kB
react-card
Card - All
53.205 kB
15.27 kB
react-card
Card
48.898 kB
14.083 kB
react-card
CardFooter
7.653 kB
3.246 kB
react-card
CardHeader
8.931 kB
3.689 kB
react-card
CardPreview
7.626 kB
3.272 kB
react-combobox
Combobox
6.813 kB
2.895 kB
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
171.709 kB
48.089 kB
react-components
react-components: FluentProvider & webLightTheme
32.526 kB
10.645 kB
react-divider
Divider
15.301 kB
5.532 kB
react-image
Image
10.105 kB
3.952 kB
react-input
Input
21.538 kB
7.134 kB
react-label
Label
8.341 kB
3.487 kB
react-link
Link
11.102 kB
4.504 kB
react-menu
Menu (including children components)
103.43 kB
31.858 kB
react-menu
Menu (including selectable components)
105.785 kB
32.219 kB
react-popover
Popover
96.339 kB
29.399 kB
react-portal
Portal
6.267 kB
2.168 kB
react-positioning
usePopper
23.21 kB
8.084 kB
react-provider
FluentProvider
14.009 kB
5.25 kB
react-select
Select
7.754 kB
3.258 kB
react-slider
Slider
22.975 kB
7.769 kB
react-spinner
Spinner
6.811 kB
2.895 kB
react-switch
Switch
22.598 kB
7.642 kB
react-text
Text - Default
10.793 kB
4.23 kB
react-text
Text - Wrappers
14.107 kB
4.573 kB
react-theme
Single theme token import
69 B
89 B
react-theme
Teams: all themes
29.426 kB
6.551 kB
react-theme
Teams: Light theme
18.42 kB
5.27 kB
react-tooltip
Tooltip
42.76 kB
14.701 kB
react-utilities
SSRProvider
189 B
161 B
🤖 This report was generated against d4c33467e224fee30434372238eedf5e7f7d3866

@fabricteam
Copy link
Collaborator

fabricteam commented Feb 14, 2022

Perf Analysis (@fluentui/react)

No significant results to display.

All results

Scenario Render type Master Ticks PR Ticks Iterations Status
Avatar mount 765 758 5000
BaseButton mount 812 816 5000
Breadcrumb mount 2306 2323 1000
ButtonNext mount 422 399 5000
Checkbox mount 1348 1347 5000
CheckboxBase mount 1126 1123 5000
ChoiceGroup mount 4156 4117 5000
ComboBox mount 863 853 1000
CommandBar mount 8785 8918 1000
ContextualMenu mount 7315 7411 1000
DefaultButton mount 977 985 5000
DetailsRow mount 3236 3227 5000
DetailsRowFast mount 3299 3251 5000
DetailsRowNoStyles mount 3072 3069 5000
Dialog mount 1925 1949 1000
DocumentCardTitle mount 149 161 1000
Dropdown mount 2762 2769 5000
FluentProviderNext mount 1731 1764 5000
FluentProviderWithTheme mount 155 120 10
FluentProviderWithTheme virtual-rerender 106 89 10
FluentProviderWithTheme virtual-rerender-with-unmount 185 166 10
FocusTrapZone mount 1638 1619 5000
FocusZone mount 1579 1587 5000
IconButton mount 1514 1516 5000
Label mount 325 328 5000
Layer mount 2599 2590 5000
Link mount 423 428 5000
MakeStyles mount 1455 1476 50000
MenuButton mount 1294 1276 5000
MessageBar mount 1745 1764 5000
Nav mount 2816 2831 1000
OverflowSet mount 979 972 5000
Panel mount 1885 1848 1000
Persona mount 740 745 1000
Pivot mount 1261 1264 1000
PrimaryButton mount 1125 1126 5000
Rating mount 6597 6670 5000
SearchBox mount 1181 1151 5000
Shimmer mount 2247 2213 5000
Slider mount 1727 1724 5000
SpinButton mount 4317 4294 5000
Spinner mount 394 387 5000
SplitButton mount 2705 2739 5000
Stack mount 480 490 5000
StackWithIntrinsicChildren mount 2015 2052 5000
StackWithTextChildren mount 4561 4581 5000
SwatchColorPicker mount 9939 9997 5000
TagPicker mount 2238 2294 5000
TeachingBubble mount 11393 11406 5000
Text mount 396 399 5000
TextField mount 1222 1226 5000
ThemeProvider mount 1024 1048 5000
ThemeProvider virtual-rerender 552 542 5000
ThemeProvider virtual-rerender-with-unmount 1645 1628 5000
Toggle mount 726 741 5000
buttonNative mount 140 134 5000

const [props, setProps] = React.useState<IFocusTrapZoneProps | undefined>();

React.useEffect(() => {
(window as FTZTestWindow).setProps = setProps;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly don't think this is bad, I guess a more 'proper' way would be to use storybook to manage the globals

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know how to manage globals with storybook? I tried setting up the story to accept parameters, which can be set in the query, but IIRC the issue with that was cy.loadStory didn't have an API to set query params.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hotell and wrote some code to handle globals for the theme picker in the v9 storybook docs, you can take a look in react-storybook-addon, but you don't need to do query params to manage storybook globals.

That being said, SB globals can be ugly to manage, that's why I said I honestly don't think it's that bad

Copy link
Member Author

@ecraig12345 ecraig12345 Feb 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: All my local --mode open runs with the previous approach (setting a global but not cleaning it up) worked fine, but a lot of the tests failed in headless mode in the PR due to a timing issue. I guess because runs in a browser UI are slower, each run always got the current test's version of setProps, but in headless runs, a lot of times the test would start running and call the previous test's setProps before the story had time to finish rendering and set a new one.

Fix is:

  • define the global inside a one-time effect WITH cleanup that deletes the global
  • in the test, use window.should not window.then to wrap the setProps call, so that it retries if the global isn't ready yet

I wrote a helper that handles this and updated all the tests to use it. If we end up porting more tests that need global functions, the helper could be made a bit more generic and moved to a shared location.

setProps({ disableFirstFocus: true });

// For some reason this doesn't work:
// cy.focused().should('match', 'body');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the actual focused item in that case ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The body. document.activeElement is body if you inspect it, but that assertion doesn't match for some reason.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah but if you run the test in the cypres ui, it should tell you what the result of cy.focused() is, just curious, maybe we're matching the wrong thing.... or even what we're expecting to be focused isn't that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC it says focused() is body but it doesn't match that selector. Maybe I'm misunderstanding what match does but it seemed like it should match any CSS selector.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps this ?

cy.get('body').should('have.focus')

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@ecraig12345 ecraig12345 Feb 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope (╯°□°)╯︵ ┻━┻

(to be clear that's directed at cypress not you)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the error messages for various attempted assertions and thinking about it some more, I think the issue is that it's checking focus with :focus rather than looking at document.activeElement. Apparently even if body is document.activeElement it doesn't get :focus. https://codepen.io/ecraig12345/pen/jOavBRw

Since cy.focused() returns a jquery object with the matches, cy.focused().should('have.length', 0) works.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh a bit better: cy.focused().should('not.exist')

@ecraig12345 ecraig12345 requested a review from a team as a code owner February 16, 2022 08:25
@@ -105,8 +105,12 @@ jobs:

# only run e2e tests when the appropriate storybook is published by scoping to relevant packages
- script: |
yarn e2e $(sinceArg) --scope @fluentui/react-components --scope @fluentui/react
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having two packages as scopes caused this PR's first build to try and run tests for deps of react-components even though react-components hadn't been built or deployed. Hopefully splitting into two steps will fix it. (Problem is it won't be possible to tell in this PR, because changing a pipeline file makes the PR build all packages.)

@andrefcdias andrefcdias self-requested a review February 16, 2022 13:30
@ling1726 ling1726 closed this Feb 17, 2022
@ling1726 ling1726 reopened this Feb 17, 2022
@fabricteam
Copy link
Collaborator

fabricteam commented Feb 25, 2022

Perf Analysis (@fluentui/react-northstar)

Perf tests with no regressions
Scenario Current PR Ticks Baseline Ticks Ratio
ButtonMinimalPerf.default 168 125 1.34:1
AttachmentMinimalPerf.default 160 133 1.2:1
BoxMinimalPerf.default 306 287 1.07:1
DropdownMinimalPerf.default 2609 2445 1.07:1
AccordionMinimalPerf.default 148 139 1.06:1
AlertMinimalPerf.default 226 214 1.06:1
CardMinimalPerf.default 485 461 1.05:1
DatepickerMinimalPerf.default 4833 4590 1.05:1
PortalMinimalPerf.default 161 153 1.05:1
IconMinimalPerf.default 538 511 1.05:1
TreeWith60ListItems.default 162 155 1.05:1
ButtonOverridesMissPerf.default 1395 1336 1.04:1
DividerMinimalPerf.default 310 299 1.04:1
FormMinimalPerf.default 345 333 1.04:1
ImageMinimalPerf.default 325 312 1.04:1
ListWith60ListItems.default 563 542 1.04:1
RefMinimalPerf.default 210 202 1.04:1
DropdownManyItemsPerf.default 601 581 1.03:1
FlexMinimalPerf.default 257 249 1.03:1
ListNestedPerf.default 479 464 1.03:1
LoaderMinimalPerf.default 597 581 1.03:1
ReactionMinimalPerf.default 332 321 1.03:1
ToolbarMinimalPerf.default 803 783 1.03:1
AttachmentSlotsPerf.default 911 896 1.02:1
EmbedMinimalPerf.default 3476 3412 1.02:1
HeaderMinimalPerf.default 314 309 1.02:1
InputMinimalPerf.default 1119 1096 1.02:1
ProviderMinimalPerf.default 977 960 1.02:1
TextMinimalPerf.default 306 299 1.02:1
CustomToolbarPrototype.default 3569 3491 1.02:1
VideoMinimalPerf.default 541 528 1.02:1
CarouselMinimalPerf.default 406 403 1.01:1
ListMinimalPerf.default 447 441 1.01:1
MenuMinimalPerf.default 722 712 1.01:1
SplitButtonMinimalPerf.default 3654 3626 1.01:1
TableManyItemsPerf.default 1633 1614 1.01:1
TableMinimalPerf.default 352 350 1.01:1
TextAreaMinimalPerf.default 423 420 1.01:1
TooltipMinimalPerf.default 886 875 1.01:1
ChatDuplicateMessagesPerf.default 256 255 1:1
LayoutMinimalPerf.default 306 306 1:1
MenuButtonMinimalPerf.default 1400 1398 1:1
SegmentMinimalPerf.default 301 301 1:1
SliderMinimalPerf.default 1424 1422 1:1
CheckboxMinimalPerf.default 2224 2246 0.99:1
GridMinimalPerf.default 291 293 0.99:1
LabelMinimalPerf.default 331 335 0.99:1
ListCommonPerf.default 536 544 0.99:1
PopupMinimalPerf.default 517 524 0.99:1
ProviderMergeThemesPerf.default 1484 1505 0.99:1
TreeMinimalPerf.default 677 685 0.99:1
ItemLayoutMinimalPerf.default 992 1017 0.98:1
RadioGroupMinimalPerf.default 378 384 0.98:1
SkeletonMinimalPerf.default 305 311 0.98:1
DialogMinimalPerf.default 623 644 0.97:1
RosterPerf.default 999 1033 0.97:1
StatusMinimalPerf.default 574 592 0.97:1
ButtonSlotsPerf.default 428 447 0.96:1
ChatMinimalPerf.default 609 633 0.96:1
ChatWithPopoverPerf.default 315 335 0.94:1
HeaderSlotsPerf.default 614 651 0.94:1
AnimationMinimalPerf.default 411 458 0.9:1
AvatarMinimalPerf.default 152 170 0.89:1

cy.focused().should('have.text', 'last');
});

// TODO: investigate why this intermittently fails and re-enable.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After more investigation, I think the test is flaky because of bugs or timing issues in the function component conversion. That will be fixed and the test enabled in an upcoming PR (the change is ready to go, but it will be hard to review until this PR goes in).


// wait for first focus to finish to avoid timing issue
cy.focused().should('have.text', 'first');

Copy link
Member

@khmakoto khmakoto Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: You could maybe click mid or last before clicking outside so that you have a change from mid or last to first instead of having first twice in a row.

Copy link
Member Author

@ecraig12345 ecraig12345 Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason that makes the rest of the test stop working, which may be due to function component conversion bugs. I'll leave it as-is for now but try that suggestion in the version with bug fixes.


/**
* Tab and shift-tab wrap at extreme ends of the FTZ:
*
* can tab across FocusZones with different button structures
* can between multiple FocusZones with different button structures
Copy link
Member

@khmakoto khmakoto Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
* can between multiple FocusZones with different button structures
* can tab between multiple FocusZones with different button structures

Comment on lines +302 to +305
// shift+tab focuses the first bumper (since the buttons inside aren't keyboard-focusable)
// => sends focus to first element
cy.realPress(['Shift', 'Tab']);
cy.focused().should('have.text', 'first');
Copy link
Member

@khmakoto khmakoto Mar 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Maybe it'd be more informative to go to last instead of mid, because doing a Shift + Tab on mid with focusable buttons would also lead to first, whereas it would go to mid when doing a Shift + Tab on last with focusable buttons.

@ecraig12345 ecraig12345 enabled auto-merge (squash) March 2, 2022 23:12
@ecraig12345 ecraig12345 merged commit f52f47c into microsoft:master Mar 2, 2022
@ecraig12345 ecraig12345 deleted the cypress-ftz branch March 3, 2022 01:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants