Skip to content

Commit 9eca6a1

Browse files
authored
Feat: Add cypress e2e tests for MenuGrid (#35553)
1 parent 36594a8 commit 9eca6a1

File tree

7 files changed

+218
-2
lines changed

7 files changed

+218
-2
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "none",
3+
"comment": "Add cypress e2e tests for MenuGrid",
4+
"packageName": "@fluentui/react-menu-grid-preview",
5+
"email": "[email protected]",
6+
"dependentChangeType": "none"
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { baseConfig } from '@fluentui/scripts-cypress';
2+
3+
export default baseConfig;

packages/react-components/react-menu-grid-preview/library/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"@fluentui/eslint-plugin": "*",
2222
"@fluentui/react-conformance": "*",
2323
"@fluentui/react-conformance-griffel": "*",
24-
"@fluentui/scripts-api-extractor": "*"
24+
"@fluentui/scripts-api-extractor": "*",
25+
"@fluentui/scripts-cypress": "*"
2526
},
2627
"dependencies": {
2728
"@fluentui/keyboard-keys": "^9.0.8",
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import * as React from 'react';
2+
import { mount as mountBase } from '@fluentui/scripts-cypress';
3+
4+
import { FluentProvider } from '@fluentui/react-provider';
5+
import { teamsLightTheme } from '@fluentui/react-theme';
6+
7+
import { Menu, MenuTrigger, MenuList, MenuItem, MenuPopover } from '@fluentui/react-components';
8+
import { MenuGrid, MenuGridItem, MenuGridCell, MenuGridRow } from '@fluentui/react-menu-grid-preview';
9+
import type { JSXElement } from '@fluentui/react-utilities';
10+
11+
const mount = (element: JSXElement) => {
12+
mountBase(<FluentProvider theme={teamsLightTheme}>{element}</FluentProvider>);
13+
};
14+
15+
const menuSelector = '[role="menu"]';
16+
const menuGridItemSelector = '[role="row"]';
17+
18+
const items = ['Olivia Carter', 'Liam Thompson', 'Sophia Martinez', 'Noah Patel', 'Emma Robinson'];
19+
20+
const DefaultExample = () => {
21+
return (
22+
<MenuGrid>
23+
{items.map((name, index) => (
24+
<MenuGridItem
25+
key={name}
26+
id={`row${index}`}
27+
firstSubAction={<button>Profile for {name}</button>}
28+
secondSubAction={<button>Remove {name}</button>}
29+
>
30+
{name}
31+
</MenuGridItem>
32+
))}
33+
</MenuGrid>
34+
);
35+
};
36+
37+
describe('MenuGrid', () => {
38+
it('should be able to tab in/out of the MenuGrid', () => {
39+
mount(
40+
<>
41+
<button className="first">Foo</button>
42+
<DefaultExample />
43+
<button className="last">Bar</button>
44+
</>,
45+
);
46+
47+
cy.get('button.first').first().focus().realPress('Tab');
48+
cy.get(menuGridItemSelector).first().should('be.focused').realPress('Tab');
49+
cy.get('button.last').first().should('be.focused');
50+
});
51+
52+
it('should be able to navigate the MenuGrid', () => {
53+
mount(<DefaultExample />);
54+
cy.get('body')
55+
.click()
56+
.get(menuGridItemSelector)
57+
.first()
58+
.focus()
59+
.focused()
60+
.should('have.id', 'row0')
61+
.realPress('ArrowDown')
62+
.focused()
63+
.should('have.id', 'row1')
64+
.realPress('ArrowDown')
65+
.focused()
66+
.should('have.id', 'row2')
67+
.realPress('ArrowRight')
68+
.focused()
69+
.should('have.text', 'Profile for Sophia Martinez')
70+
.realPress('ArrowRight')
71+
.focused()
72+
.should('have.text', 'Remove Sophia Martinez');
73+
});
74+
});
75+
76+
const Submenu = () => {
77+
return (
78+
<Menu>
79+
<MenuTrigger>
80+
<button
81+
onKeyDown={event => {
82+
if (event.key === 'ArrowDown') {
83+
// Prevent arrow down from opening the menu to enable navigation in grid instead
84+
event.preventDefault();
85+
}
86+
}}
87+
>
88+
More actions
89+
</button>
90+
</MenuTrigger>
91+
<MenuPopover>
92+
<MenuList>
93+
<MenuItem>Show profile</MenuItem>
94+
<MenuItem>Audio call</MenuItem>
95+
<MenuItem>Video call</MenuItem>
96+
<MenuItem>Remove</MenuItem>
97+
</MenuList>
98+
</MenuPopover>
99+
</Menu>
100+
);
101+
};
102+
103+
const WithSubmenuExample = () => {
104+
return (
105+
<MenuGrid>
106+
{items.map(name => (
107+
<MenuGridItem key={name} firstSubAction={<Submenu />}>
108+
{name}
109+
</MenuGridItem>
110+
))}
111+
</MenuGrid>
112+
);
113+
};
114+
115+
const MoreComplexExample = () => {
116+
return (
117+
<MenuGrid>
118+
{items.map((name, index) => (
119+
<MenuGridRow key={name} id={`row${index}`}>
120+
<MenuGridCell>
121+
<button>Profile for {name}</button>
122+
</MenuGridCell>
123+
<MenuGridCell>{name}</MenuGridCell>
124+
<MenuGridCell>
125+
<button>Audio call</button>
126+
</MenuGridCell>
127+
<MenuGridCell>
128+
<button>Video call</button>
129+
</MenuGridCell>
130+
<MenuGridCell>
131+
<button>Remove {name}</button>
132+
</MenuGridCell>
133+
</MenuGridRow>
134+
))}
135+
</MenuGrid>
136+
);
137+
};
138+
139+
describe('More complex menus', () => {
140+
it('should be able to navigate the MenuGrid', () => {
141+
mount(<MoreComplexExample />);
142+
cy.get('body')
143+
.click()
144+
.get(menuGridItemSelector)
145+
.first()
146+
.focus()
147+
.focused()
148+
.should('have.id', 'row0')
149+
.realPress('ArrowDown')
150+
.focused()
151+
.should('have.id', 'row1')
152+
.realPress('ArrowDown')
153+
.focused()
154+
.should('have.id', 'row2')
155+
.realPress('ArrowRight')
156+
.focused()
157+
.should('have.text', 'Profile for Sophia Martinez')
158+
.realPress('ArrowRight')
159+
.focused()
160+
.should('have.text', 'Audio call')
161+
.realPress('ArrowRight')
162+
.focused()
163+
.should('have.text', 'Video call')
164+
.realPress('ArrowRight')
165+
.focused()
166+
.should('have.text', 'Remove Sophia Martinez');
167+
});
168+
});
169+
170+
describe('With MenuList submenus', () => {
171+
it('should not open a submenu trigger with ArrowDown', () => {
172+
mount(<WithSubmenuExample />);
173+
cy.get(menuGridItemSelector).first().focus().realPress('ArrowRight').realPress('ArrowDown');
174+
cy.get(menuSelector).should('have.length', 0);
175+
cy.focused().should('have.attr', 'role', 'row');
176+
});
177+
178+
it('should focus next grid row from a submenu trigger with ArrowDown', () => {
179+
mount(<WithSubmenuExample />);
180+
cy.get(menuGridItemSelector).first().focus().realPress('ArrowRight').realPress('ArrowDown');
181+
cy.get(menuGridItemSelector).eq(1).should('be.focused');
182+
});
183+
184+
it('should open a submenu trigger with Enter', () => {
185+
mount(<WithSubmenuExample />);
186+
cy.get(menuGridItemSelector).first().focus().realPress('ArrowRight').realPress('Enter');
187+
cy.get(menuSelector).should('have.length', 1);
188+
cy.focused().should('have.attr', 'role', 'menuitem');
189+
});
190+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"isolatedModules": false,
5+
"types": ["node", "cypress", "cypress-real-events"],
6+
"typeRoots": ["../../../../node_modules", "../../../../node_modules/@types"],
7+
"lib": ["ES2019", "dom"]
8+
},
9+
"include": ["**/*.cy.ts", "**/*.cy.tsx"]
10+
}

packages/react-components/react-menu-grid-preview/library/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
},
1818
{
1919
"path": "./tsconfig.spec.json"
20+
},
21+
{
22+
"path": "./tsconfig.cy.json"
2023
}
2124
]
2225
}

packages/react-components/react-menu-grid-preview/library/tsconfig.lib.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"**/*.test.ts",
1717
"**/*.test.tsx",
1818
"**/*.stories.ts",
19-
"**/*.stories.tsx"
19+
"**/*.stories.tsx",
20+
"**/*.cy.ts",
21+
"**/*.cy.tsx"
2022
],
2123
"include": ["./src/**/*.ts", "./src/**/*.tsx"]
2224
}

0 commit comments

Comments
 (0)