Skip to content

Commit 1949260

Browse files
committed
fix: use onExitTutorial instead of UseEffect
1 parent 66addb3 commit 1949260

File tree

3 files changed

+71
-26
lines changed

3 files changed

+71
-26
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
import { BasePageObject } from '@cloudscape-design/browser-test-tools/page-objects';
4+
import useBrowser from '@cloudscape-design/browser-test-tools/use-browser';
5+
6+
import createWrapper from '../../../lib/components/test-utils/selectors';
7+
8+
class TutorialPanelFocusPage extends BasePageObject {
9+
getTutorialPanel() {
10+
return createWrapper().findAppLayout().findTools().findTutorialPanel();
11+
}
12+
13+
async startFirstTutorial() {
14+
const tutorialPanel = this.getTutorialPanel();
15+
await this.click(tutorialPanel.findTutorials().get(1).findStartButton().toSelector());
16+
}
17+
18+
async exitTutorial() {
19+
const tutorialPanel = this.getTutorialPanel();
20+
await this.click(tutorialPanel.findDismissButton().toSelector());
21+
}
22+
23+
getTutorialListHeader() {
24+
const tutorialPanel = this.getTutorialPanel();
25+
return tutorialPanel.find('[tabindex="-1"]').toSelector();
26+
}
27+
28+
isTutorialListHeaderFocused() {
29+
const headerSelector = this.getTutorialListHeader();
30+
return this.isFocused(headerSelector);
31+
}
32+
33+
async waitForTutorialListToLoad() {
34+
const tutorialPanel = this.getTutorialPanel();
35+
await this.waitForVisible(tutorialPanel.findTutorials().get(1).toSelector());
36+
}
37+
38+
async waitForTutorialDetailToLoad() {
39+
const tutorialPanel = this.getTutorialPanel();
40+
await this.waitForVisible(tutorialPanel.findDismissButton().toSelector());
41+
}
42+
}
43+
44+
const setupTest = (testFn: (page: TutorialPanelFocusPage) => Promise<void>) =>
45+
useBrowser(async browser => {
46+
const page = new TutorialPanelFocusPage(browser);
47+
await browser.url('#/light/onboarding/with-app-layout');
48+
await page.waitForTutorialListToLoad();
49+
await testFn(page);
50+
});
51+
52+
describe('TutorialPanel Focus Management', () => {
53+
test(
54+
'restores focus to panel header when exiting tutorial',
55+
setupTest(async page => {
56+
await page.startFirstTutorial();
57+
await page.waitForTutorialDetailToLoad();
58+
59+
await page.exitTutorial();
60+
await page.waitForTutorialListToLoad();
61+
62+
await expect(page.isTutorialListHeaderFocused()).resolves.toBe(true);
63+
})
64+
);
65+
});

src/tutorial-panel/__tests__/tutorial-panel.test.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33
import React from 'react';
4-
import { render, waitFor } from '@testing-library/react';
4+
import { render } from '@testing-library/react';
55

66
import {
77
HotspotContext as HotspotContextType,
@@ -195,20 +195,6 @@ describe('tutorial detail view', () => {
195195
expect(completedScreen).toHaveTextContent('COMPLETION_SCREEN_TITLE');
196196
expect(completedScreen).toHaveTextContent('COMPLETED_SCREEN_DESCRIPTION_TEST');
197197
});
198-
199-
test('restores focus to panel header when exiting tutorial', async () => {
200-
const { container, rerender } = renderTutorialPanelWithContext({}, { currentTutorial: getTutorials()[0] });
201-
202-
rerender(
203-
<HotspotContext.Provider value={getContext({ currentTutorial: null })}>
204-
<TutorialPanel i18nStrings={i18nStrings} downloadUrl="DOWNLOAD_URL" tutorials={getTutorials()} />
205-
</HotspotContext.Provider>
206-
);
207-
208-
await waitFor(() => {
209-
expect(container.querySelector('[tabindex="-1"]')).toHaveFocus();
210-
});
211-
});
212198
});
213199

214200
describe('URL sanitization', () => {

src/tutorial-panel/index.tsx

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33
'use client';
4-
import React, { useContext, useEffect, useRef } from 'react';
4+
import React, { useContext, useRef } from 'react';
55
import clsx from 'clsx';
66

77
import { hotspotContext } from '../annotation-context/context';
@@ -29,15 +29,6 @@ export default function TutorialPanel({
2929
const baseProps = getBaseProps(restProps);
3030
const context = useContext(hotspotContext);
3131
const headerRef = useRef<HTMLHeadingElement>(null);
32-
const previousTutorialRef = useRef(context.currentTutorial);
33-
34-
// Restore focus to the tutorial panel header when exiting a tutorial
35-
useEffect(() => {
36-
if (!context.currentTutorial && previousTutorialRef.current) {
37-
headerRef.current?.focus({ preventScroll: true });
38-
}
39-
previousTutorialRef.current = context.currentTutorial;
40-
}, [context.currentTutorial]);
4132

4233
return (
4334
<>
@@ -46,7 +37,10 @@ export default function TutorialPanel({
4637
<TutorialDetailView
4738
i18nStrings={i18nStrings}
4839
tutorial={context.currentTutorial}
49-
onExitTutorial={context.onExitTutorial}
40+
onExitTutorial={e => {
41+
context.onExitTutorial(e);
42+
requestAnimationFrame(() => headerRef.current?.focus({ preventScroll: true }));
43+
}}
5044
currentStepIndex={context.currentStepIndex}
5145
onFeedbackClick={onFeedbackClick}
5246
/>

0 commit comments

Comments
 (0)