Skip to content

Commit c77f698

Browse files
committed
test(tearsheet): implement tests for enablePresence prop
1 parent 8d1ba7e commit c77f698

File tree

4 files changed

+351
-0
lines changed

4 files changed

+351
-0
lines changed

e2e/components/Tearsheet/Tearsheet-test.avt.e2e.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,4 +370,68 @@ test.describe('Tearsheet @avt', () => {
370370
// Expect narrow Tearsheet has aria-hidden='true' attribute
371371
await expect(narrowTs).toHaveAttribute('aria-hidden', 'true');
372372
});
373+
374+
test('@avt-enable-presence-dom-removal', async ({ page }) => {
375+
await visitStory(page, {
376+
component: 'Tearsheet',
377+
id: 'components-tearsheet--tearsheet',
378+
globals: {
379+
carbonTheme: 'white',
380+
},
381+
args: {
382+
enablePresence: true,
383+
},
384+
});
385+
386+
const modalElement = page.locator(`.${carbon.prefix}--modal.is-visible`);
387+
388+
// Expect Tearsheet to be in the viewport
389+
await expect(modalElement).toBeInViewport();
390+
391+
// Pressing 'Escape' key to close the Tearsheet
392+
await page.keyboard.press('Escape');
393+
394+
// Wait for animation to complete
395+
await page.waitForTimeout(500);
396+
await page.screenshot({ animations: 'disabled' });
397+
398+
// With enablePresence, the DOM element should be removed after exit
399+
const allModals = await page.locator(`.${carbon.prefix}--modal`).count();
400+
expect(allModals).toBe(0);
401+
});
402+
403+
test('@avt-enable-presence-reopen-animation', async ({ page }) => {
404+
await visitStory(page, {
405+
component: 'Tearsheet',
406+
id: 'components-tearsheet--tearsheet',
407+
globals: {
408+
carbonTheme: 'white',
409+
},
410+
args: {
411+
enablePresence: true,
412+
},
413+
});
414+
415+
const modalElement = page.locator(`.${carbon.prefix}--modal.is-visible`);
416+
417+
// Expect Tearsheet to be in the viewport
418+
await expect(modalElement).toBeInViewport();
419+
420+
// Close the Tearsheet
421+
await page.keyboard.press('Escape');
422+
423+
// Wait for animation and DOM removal
424+
await page.waitForTimeout(500);
425+
426+
// Reopen the Tearsheet
427+
const openButton = page.getByText('Open Tearsheet');
428+
await openButton.click();
429+
430+
// Wait for entrance animation
431+
await page.waitForTimeout(100);
432+
await page.screenshot({ animations: 'disabled' });
433+
434+
// Verify Tearsheet is visible again with animation
435+
await expect(modalElement).toBeInViewport();
436+
});
373437
});

e2e/components/Tearsheet/TearsheetNarrow-test.avt.e2e.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,69 @@ test.describe('TearsheetNarrow @avt', () => {
233233

234234
await expect(tearsheet3).toBeInViewport();
235235
});
236+
237+
test('@avt-enable-presence-dom-removal', async ({ page }) => {
238+
await visitStory(page, {
239+
component: 'TearsheetNarrow',
240+
id: 'components-tearsheet-tearsheetnarrow--tearsheet-narrow',
241+
globals: {
242+
carbonTheme: 'white',
243+
},
244+
args: {
245+
enablePresence: true,
246+
},
247+
});
248+
249+
const modalElement = page.locator(`.${carbon.prefix}--modal.is-visible`);
250+
251+
// Default opened in story
252+
await page.screenshot({ animations: 'disabled' });
253+
await expect(modalElement).toBeInViewport();
254+
255+
// Pressing 'Escape' key to close the Tearsheet
256+
await page.keyboard.press('Escape');
257+
258+
// Wait for animation to complete
259+
await page.waitForTimeout(500);
260+
await page.screenshot({ animations: 'disabled' });
261+
262+
// With enablePresence, the DOM element should be removed after exit
263+
const allModals = await page.locator(`.${carbon.prefix}--modal`).count();
264+
expect(allModals).toBe(0);
265+
});
266+
267+
test('@avt-enable-presence-reopen-animation', async ({ page }) => {
268+
await visitStory(page, {
269+
component: 'TearsheetNarrow',
270+
id: 'components-tearsheet-tearsheetnarrow--tearsheet-narrow',
271+
globals: {
272+
carbonTheme: 'white',
273+
},
274+
args: {
275+
enablePresence: true,
276+
},
277+
});
278+
279+
const modalElement = page.locator(`.${carbon.prefix}--modal.is-visible`);
280+
281+
// Default opened in story
282+
await expect(modalElement).toBeInViewport();
283+
284+
// Close the Tearsheet
285+
await page.keyboard.press('Escape');
286+
287+
// Wait for animation and DOM removal
288+
await page.waitForTimeout(500);
289+
290+
// Reopen the Tearsheet
291+
const openButton = page.getByText('Open Tearsheet');
292+
await openButton.click();
293+
294+
// Wait for entrance animation
295+
await page.waitForTimeout(100);
296+
await page.screenshot({ animations: 'disabled' });
297+
298+
// Verify Tearsheet is visible again with animation
299+
await expect(modalElement).toBeInViewport();
300+
});
236301
});

packages/ibm-products/src/components/Tearsheet/Tearsheet.test.js

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,147 @@ describe(componentName, () => {
395395
expect(tab.textContent).toEqual(tabContent);
396396
});
397397
});
398+
399+
describe('enablePresence', () => {
400+
it('renders tearsheet when enablePresence is true and open is true', () => {
401+
render(
402+
<Tearsheet
403+
open
404+
enablePresence
405+
title="Test Tearsheet"
406+
closeIconDescription="Close"
407+
>
408+
{children}
409+
</Tearsheet>
410+
);
411+
expect(screen.getByText(childFragment)).toBeInTheDocument();
412+
});
413+
414+
it('does not render tearsheet initially when enablePresence is true and open is false', () => {
415+
render(
416+
<Tearsheet
417+
open={false}
418+
enablePresence
419+
title="Test Tearsheet"
420+
closeIconDescription="Close"
421+
>
422+
{children}
423+
</Tearsheet>
424+
);
425+
expect(screen.queryByText(childFragment)).not.toBeInTheDocument();
426+
});
427+
428+
it('removes tearsheet from DOM after closing when enablePresence is true', async () => {
429+
const { rerender } = render(
430+
<Tearsheet
431+
open
432+
enablePresence
433+
title="Test Tearsheet"
434+
closeIconDescription="Close"
435+
>
436+
{children}
437+
</Tearsheet>
438+
);
439+
440+
// Tearsheet should be visible
441+
expect(screen.getByText(childFragment)).toBeInTheDocument();
442+
443+
// Close the tearsheet
444+
rerender(
445+
<Tearsheet
446+
open={false}
447+
enablePresence
448+
title="Test Tearsheet"
449+
closeIconDescription="Close"
450+
>
451+
{children}
452+
</Tearsheet>
453+
);
454+
455+
// Wait for transition to complete (using act to handle state updates)
456+
await act(async () => {
457+
// Simulate transitionend event
458+
const modal = document.querySelector(`.${carbon.prefix}--modal`);
459+
if (modal) {
460+
const event = new Event('transitionend', { bubbles: true });
461+
Object.defineProperty(event, 'target', {
462+
value: modal.querySelector(`.${blockClass}__container`),
463+
enumerable: true,
464+
});
465+
Object.defineProperty(event, 'propertyName', {
466+
value: 'transform',
467+
enumerable: true,
468+
});
469+
modal.dispatchEvent(event);
470+
}
471+
// Small delay to allow state updates
472+
await new Promise((resolve) => setTimeout(resolve, 50));
473+
});
474+
475+
// Tearsheet should be removed from DOM
476+
expect(screen.queryByText(childFragment)).not.toBeInTheDocument();
477+
});
478+
479+
it('keeps tearsheet in DOM when enablePresence is false (default behavior)', async () => {
480+
const { rerender } = render(
481+
<Tearsheet open title="Test Tearsheet" closeIconDescription="Close">
482+
{children}
483+
</Tearsheet>
484+
);
485+
486+
// Tearsheet should be visible
487+
expect(screen.getByText(childFragment)).toBeInTheDocument();
488+
489+
// Close the tearsheet
490+
rerender(
491+
<Tearsheet
492+
open={false}
493+
title="Test Tearsheet"
494+
closeIconDescription="Close"
495+
>
496+
{children}
497+
</Tearsheet>
498+
);
499+
500+
// Tearsheet should still be in DOM (just hidden)
501+
await act(async () => {
502+
await new Promise((resolve) => setTimeout(resolve, 50));
503+
});
504+
505+
// With enablePresence=false, the modal stays in DOM
506+
const modal = document.querySelector(`.${carbon.prefix}--modal`);
507+
expect(modal).toBeInTheDocument();
508+
});
509+
510+
it('delays opening animation when enablePresence is true', async () => {
511+
jest.useFakeTimers();
512+
513+
render(
514+
<Tearsheet
515+
open
516+
enablePresence
517+
title="Test Tearsheet"
518+
closeIconDescription="Close"
519+
>
520+
{children}
521+
</Tearsheet>
522+
);
523+
524+
// Initially, the modal might not be fully open due to the delay
525+
const modal = document.querySelector(`.${carbon.prefix}--modal`);
526+
expect(modal).toBeInTheDocument();
527+
528+
// Fast-forward time to trigger the delayed open
529+
act(() => {
530+
jest.advanceTimersByTime(20);
531+
});
532+
533+
// Now the modal should be fully open
534+
expect(screen.getByText(childFragment)).toBeInTheDocument();
535+
536+
jest.useRealTimers();
537+
});
538+
});
398539
});
399540

400541
describe(componentNameNarrow, () => {
@@ -403,6 +544,71 @@ describe(componentNameNarrow, () => {
403544
});
404545

405546
commonTests(TearsheetNarrow, componentNameNarrow, {}, true);
547+
548+
describe('enablePresence', () => {
549+
it('renders narrow tearsheet when enablePresence is true and open is true', () => {
550+
render(
551+
<TearsheetNarrow
552+
open
553+
enablePresence
554+
title="Test Narrow Tearsheet"
555+
closeIconDescription="Close"
556+
>
557+
{children}
558+
</TearsheetNarrow>
559+
);
560+
expect(screen.getByText(childFragment)).toBeInTheDocument();
561+
});
562+
563+
it('removes narrow tearsheet from DOM after closing when enablePresence is true', async () => {
564+
const { rerender } = render(
565+
<TearsheetNarrow
566+
open
567+
enablePresence
568+
title="Test Narrow Tearsheet"
569+
closeIconDescription="Close"
570+
>
571+
{children}
572+
</TearsheetNarrow>
573+
);
574+
575+
// Tearsheet should be visible
576+
expect(screen.getByText(childFragment)).toBeInTheDocument();
577+
578+
// Close the tearsheet
579+
rerender(
580+
<TearsheetNarrow
581+
open={false}
582+
enablePresence
583+
title="Test Narrow Tearsheet"
584+
closeIconDescription="Close"
585+
>
586+
{children}
587+
</TearsheetNarrow>
588+
);
589+
590+
// Wait for transition to complete
591+
await act(async () => {
592+
const modal = document.querySelector(`.${carbon.prefix}--modal`);
593+
if (modal) {
594+
const event = new Event('transitionend', { bubbles: true });
595+
Object.defineProperty(event, 'target', {
596+
value: modal.querySelector(`.${blockClass}__container`),
597+
enumerable: true,
598+
});
599+
Object.defineProperty(event, 'propertyName', {
600+
value: 'transform',
601+
enumerable: true,
602+
});
603+
modal.dispatchEvent(event);
604+
}
605+
await new Promise((resolve) => setTimeout(resolve, 50));
606+
});
607+
608+
// Tearsheet should be removed from DOM
609+
expect(screen.queryByText(childFragment)).not.toBeInTheDocument();
610+
});
611+
});
406612
});
407613

408614
describe(componentNameCreateNarrow, () => {

packages/ibm-products/src/components/Tearsheet/TearsheetNarrow.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ export interface TearsheetNarrowProps extends PropsWithChildren {
7373
*/
7474
hasCloseIcon?: boolean;
7575

76+
/**
77+
* Enable presence mode to remove the tearsheet from the DOM after it exits.
78+
* When enabled, the tearsheet will be completely removed from the DOM after
79+
* the exit animation completes, rather than just being hidden. This preserves
80+
* all animations while cleaning up the DOM.
81+
*/
82+
enablePresence?: boolean;
83+
7684
/**
7785
* A label for the tearsheet, displayed in the header area of the tearsheet
7886
* to maintain context for the tearsheet (e.g. as the title changes from page
@@ -250,6 +258,14 @@ TearsheetNarrow.propTypes = {
250258
*/
251259
description: PropTypes.node,
252260

261+
/**
262+
* Enable presence mode to remove the tearsheet from the DOM after it exits.
263+
* When enabled, the tearsheet will be completely removed from the DOM after
264+
* the exit animation completes, rather than just being hidden. This preserves
265+
* all animations while cleaning up the DOM.
266+
*/
267+
enablePresence: PropTypes.bool,
268+
253269
/**
254270
* Enable a close icon ('x') in the header area of the tearsheet. By default,
255271
* a tearsheet does not display a close icon, but one should be enabled if

0 commit comments

Comments
 (0)