Skip to content

Commit 20b08c0

Browse files
layershifterclaude
andauthored
test(react-teaching-popover): add cypress e2e tests (#36028)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8185216 commit 20b08c0

7 files changed

Lines changed: 181 additions & 1 deletion

File tree

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 TeachingPopover",
4+
"packageName": "@fluentui/react-teaching-popover",
5+
"email": "olfedias@microsoft.com",
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-teaching-popover/library/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,8 @@
5252
"major",
5353
"prerelease"
5454
]
55+
},
56+
"devDependencies": {
57+
"@fluentui/scripts-cypress": "*"
5558
}
5659
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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 {
8+
TeachingPopover,
9+
TeachingPopoverTrigger,
10+
TeachingPopoverSurface,
11+
TeachingPopoverBody,
12+
TeachingPopoverTitle,
13+
} from '@fluentui/react-teaching-popover';
14+
import type { TeachingPopoverProps } from '@fluentui/react-teaching-popover';
15+
import type { JSXElement } from '@fluentui/react-utilities';
16+
17+
const mount = (element: JSXElement) => {
18+
mountBase(<FluentProvider theme={teamsLightTheme}>{element}</FluentProvider>);
19+
};
20+
21+
const triggerSelector = '[aria-expanded]';
22+
// TeachingPopover defaults to `trapFocus: true`, so the surface is rendered with role="dialog".
23+
const surfaceSelector = '[role="dialog"]';
24+
25+
describe('TeachingPopover', () => {
26+
(['uncontrolled', 'controlled'] as const).forEach(scenario => {
27+
const UncontrolledExample = () => (
28+
<TeachingPopover>
29+
<TeachingPopoverTrigger disableButtonEnhancement>
30+
<button>Trigger</button>
31+
</TeachingPopoverTrigger>
32+
<TeachingPopoverSurface>
33+
<TeachingPopoverBody>
34+
<TeachingPopoverTitle>Title</TeachingPopoverTitle>
35+
<div>This is a teaching popover</div>
36+
</TeachingPopoverBody>
37+
</TeachingPopoverSurface>
38+
</TeachingPopover>
39+
);
40+
41+
const ControlledExample = () => {
42+
const [open, setOpen] = React.useState(false);
43+
44+
return (
45+
<TeachingPopover open={open} onOpenChange={(e, data) => setOpen(data.open)}>
46+
<TeachingPopoverTrigger disableButtonEnhancement>
47+
<button>Trigger</button>
48+
</TeachingPopoverTrigger>
49+
<TeachingPopoverSurface>
50+
<TeachingPopoverBody>
51+
<TeachingPopoverTitle>Title</TeachingPopoverTitle>
52+
<div>This is a teaching popover</div>
53+
</TeachingPopoverBody>
54+
</TeachingPopoverSurface>
55+
</TeachingPopover>
56+
);
57+
};
58+
59+
describe(scenario, () => {
60+
const Example = scenario === 'controlled' ? ControlledExample : UncontrolledExample;
61+
62+
beforeEach(() => {
63+
mount(<Example />);
64+
cy.get('body').click('bottomRight');
65+
});
66+
67+
it('should open when clicked', () => {
68+
cy.get(triggerSelector).click().get(surfaceSelector).should('be.visible');
69+
});
70+
71+
(['{enter}', 'Space'] as const).forEach(key => {
72+
it(`should open with ${key}`, () => {
73+
cy.get(triggerSelector).focus().realPress(key);
74+
cy.get(surfaceSelector).should('be.visible');
75+
});
76+
});
77+
78+
it('should dismiss on click outside', () => {
79+
cy.get(triggerSelector).click().get('body').click('bottomRight').get(surfaceSelector).should('not.exist');
80+
});
81+
82+
it('should dismiss on Escape keydown', () => {
83+
cy.get(triggerSelector).click().realPress('Escape');
84+
cy.get(surfaceSelector).should('not.exist');
85+
});
86+
});
87+
});
88+
89+
describe('focus trap default', () => {
90+
it('should trap focus by default', () => {
91+
mount(
92+
<TeachingPopover>
93+
<TeachingPopoverTrigger disableButtonEnhancement>
94+
<button>Trigger</button>
95+
</TeachingPopoverTrigger>
96+
<TeachingPopoverSurface>
97+
<button>One</button>
98+
<button>Two</button>
99+
</TeachingPopoverSurface>
100+
</TeachingPopover>,
101+
);
102+
103+
cy.get(triggerSelector).focus().realPress('Enter');
104+
105+
cy.contains('One').should('have.focus').realPress('Tab');
106+
cy.contains('Two').should('have.focus').realPress('Tab');
107+
cy.contains('One').should('have.focus');
108+
});
109+
});
110+
111+
describe('updating content', () => {
112+
const Example = () => {
113+
const [visible, setVisible] = React.useState(false);
114+
const changeContent = () => setVisible(true);
115+
const onOpenChange: TeachingPopoverProps['onOpenChange'] = (e, data) => {
116+
if (data.open === false) {
117+
setVisible(false);
118+
}
119+
};
120+
121+
return (
122+
<TeachingPopover onOpenChange={onOpenChange}>
123+
<TeachingPopoverTrigger disableButtonEnhancement>
124+
<button>Trigger</button>
125+
</TeachingPopoverTrigger>
126+
<TeachingPopoverSurface>
127+
{visible ? (
128+
<div>The second panel</div>
129+
) : (
130+
<div>
131+
<button onClick={changeContent}>Action</button>
132+
</div>
133+
)}
134+
</TeachingPopoverSurface>
135+
</TeachingPopover>
136+
);
137+
};
138+
139+
it('should not close when inner content changes', () => {
140+
mount(<Example />);
141+
cy.get(triggerSelector)
142+
.click()
143+
.get(surfaceSelector)
144+
.within(() => {
145+
cy.contains('Action').click();
146+
})
147+
.get(surfaceSelector)
148+
.should('exist')
149+
.contains('The second panel');
150+
});
151+
});
152+
});
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-teaching-popover/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-teaching-popover/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)