diff --git a/pages/table/expandable-rows-test.page.tsx b/pages/table/expandable-rows-test.page.tsx index fbeea34450..99d3fed49c 100644 --- a/pages/table/expandable-rows-test.page.tsx +++ b/pages/table/expandable-rows-test.page.tsx @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { isEqual } from 'lodash'; import { useCollection } from '@cloudscape-design/collection-hooks'; @@ -31,11 +31,15 @@ import messages from '~components/i18n/messages/all.en'; import SpaceBetween from '~components/space-between'; import AppContext, { AppContextType } from '../app/app-context'; +import { enhanceWindow, WindowWithFlushResponse } from '../common/flush-response'; import { ariaLabels, getHeaderCounterText, Instance } from './expandable-rows/common'; import { createColumns, createPreferences, filteringProperties } from './expandable-rows/expandable-rows-configs'; import { allInstances } from './expandable-rows/expandable-rows-data'; import { EmptyState, getMatchesCountText, renderAriaLive } from './shared-configs'; +declare const window: WindowWithFlushResponse; +enhanceWindow(); + type LoadingState = Map; type PageContext = React.Context< @@ -51,6 +55,7 @@ type PageContext = React.Context< useProgressiveLoading: boolean; useServerMock: boolean; emulateServerError: boolean; + manualServerMock: boolean; }> >; @@ -195,6 +200,18 @@ const NESTED_PAGE_SIZE = 2; function useTableData() { const settings = usePageSettings(); const delay = settings.useServerMock ? SERVER_DELAY : 0; + const getServerResponse = useCallback( + (cb: () => void) => { + if (settings.manualServerMock) { + window.__pendingCallbacks.push(cb); + return () => {}; + } else { + const timerRef = setTimeout(cb, delay); + return () => clearTimeout(timerRef); + } + }, + [delay, settings.manualServerMock] + ); const [loading, setLoading] = useState(false); const [error, setError] = useState(false); @@ -204,13 +221,13 @@ function useTableData() { useEffect(() => { setLoading(true); setError(false); - setTimeout(() => { + return getServerResponse(() => { setReadyInstances(allInstances); setLoading(false); setError(settings.emulateServerError); - }, delay); + }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [delay, setLoading, setError, setReadyInstances]); + }, [getServerResponse, setLoading, setError, setReadyInstances]); const collectionResult = useCollection(readyInstances, { pagination: settings.usePagination ? { pageSize: ROOT_PAGE_SIZE } : undefined, @@ -245,14 +262,13 @@ function useTableData() { useEffect(() => { setLoading(true); setError(false); - const timeoutId = setTimeout(() => { + return getServerResponse(() => { setLoading(false); setReadyItems(memoItems); setError(settings.emulateServerError); - }, delay); - return () => clearTimeout(timeoutId); + }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [delay, memoItems, setLoading, setError, setReadyItems]); + }, [getServerResponse, memoItems, setLoading, setError, setReadyItems]); // Decorate path options to only show the last node and not the full path. collectionResult.propertyFilterProps.filteringOptions = collectionResult.propertyFilterProps.filteringOptions.map( @@ -277,7 +293,7 @@ function useTableData() { const loadItems = (id: string) => { setLoadingState(nextLoading(id)); if (delay) { - setTimeout(() => setLoadingState(settings.emulateServerError ? nextError(id) : nextPending(id)), delay); + getServerResponse(() => setLoadingState(settings.emulateServerError ? nextError(id) : nextPending(id))); } else { setLoadingState(nextPending(id)); } @@ -355,6 +371,7 @@ function usePageSettings() { useProgressiveLoading: urlParams.useProgressiveLoading ?? true, groupResources: urlParams.groupResources ?? true, useServerMock: urlParams.useServerMock ?? false, + manualServerMock: urlParams.manualServerMock ?? false, emulateServerError: urlParams.emulateServerError ?? false, setUrlParams, }; diff --git a/src/table/__integ__/expandable-rows.test.ts b/src/table/__integ__/expandable-rows.test.ts index 3482d4997a..7149f5e1ad 100644 --- a/src/table/__integ__/expandable-rows.test.ts +++ b/src/table/__integ__/expandable-rows.test.ts @@ -1,9 +1,10 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { BasePageObject } from '@cloudscape-design/browser-test-tools/page-objects'; + import useBrowser from '@cloudscape-design/browser-test-tools/use-browser'; import createWrapper from '../../../lib/components/test-utils/selectors'; +import { AsyncResponsePage } from '../../__integ__/page-objects/async-response-page'; const tableWrapper = createWrapper().findTable(); @@ -15,17 +16,18 @@ interface TestPageOptions { describe('Expandable rows', () => { const setupTest = ( { useProgressiveLoading = false, useServerMock = false }: TestPageOptions, - testFn: (page: BasePageObject) => Promise + testFn: (page: AsyncResponsePage) => Promise ) => { return useBrowser(async browser => { - const page = new BasePageObject(browser); + const page = new AsyncResponsePage(browser); await page.setWindowSize({ width: 1200, height: 1000 }); const query = new URLSearchParams({ useProgressiveLoading: String(useProgressiveLoading), useServerMock: String(useServerMock), + manualServerMock: String(useServerMock), }); await browser.url(`#/light/table/expandable-rows-test?${query.toString()}`); - await page.waitForVisible(tableWrapper.findBodyCell(2, 1).toSelector()); + await page.waitForVisible(tableWrapper.findColumnHeaders().get(1).toSelector()); await testFn(page); }); }; @@ -52,11 +54,16 @@ describe('Expandable rows', () => { const page3Toggle = tableWrapper.findExpandToggle(6); const getRowsCount = () => page.getElementsCount(tableWrapper.findRows().toSelector()); + // no data initially + await expect(getRowsCount()).resolves.toBe(0); + // 10 data rows + 1 loader row - await expect(getRowsCount()).resolves.toBe(10 + 1); + await page.flushResponse(); + await page.waitForAssertion(() => expect(getRowsCount()).resolves.toBe(10 + 1)); // Expand target cluster await page.click(tableWrapper.findExpandToggle(1).toSelector()); + await page.flushResponse(); await page.waitForAssertion(() => expect(getRowsCount()).resolves.toBe(12 + 2)); // Navigate to the target cluster loader @@ -66,14 +73,16 @@ describe('Expandable rows', () => { // Trigger target cluster load-more await page.keys(['Enter']); // Ensure state change occurs and the focus stays on the same cell (next load-more) - await page.waitForAssertion(() => expect(page.getFocusedElementText()).resolves.toBe('Loading items')); + await page.waitForAssertion(() => expect(page.getFocusedElementText()).resolves.toContain('Loading items')); + await page.flushResponse(); await page.waitForAssertion(() => expect(page.isFocused(page2Toggle.toSelector())).resolves.toBe(true)); await page.waitForAssertion(() => expect(getRowsCount()).resolves.toBe(14 + 2)); // Trigger subsequent loading await page.keys(['ArrowDown', 'ArrowDown', 'Enter']); // Ensure state change occurs and the focus stays on the same cell (last cluster's expand toggle) - await page.waitForAssertion(() => expect(page.getFocusedElementText()).resolves.toBe('Loading items')); + await page.waitForAssertion(() => expect(page.getFocusedElementText()).resolves.toContain('Loading items')); + await page.flushResponse(); await page.waitForAssertion(() => expect(page.isFocused(page3Toggle.toSelector())).resolves.toBe(true)); await page.waitForAssertion(() => expect(getRowsCount()).resolves.toBe(15 + 1)); })