From 0ea3f2a060e285a71d3192a679432169fbf08e76 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Tue, 1 Sep 2020 22:22:54 -0500 Subject: [PATCH] Decouple public, internal act implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the next major release, we intend to drop support for using the `act` testing helper in production. (It already fires a warning.) The rationale is that, in order for `act` to work, you must either mock the testing environment or add extra logic at runtime. Mocking the testing environment isn't ideal because it requires extra set up for the user. Extra logic at runtime is fine only in development mode — we don't want to slow down the production builds. Since most people only run their tests in development mode, dropping support for production should be fine; if there's demand, we can add it back later using a special testing build that is identical to the production build except for the additional testing logic. One blocker for removing production support is that we currently use `act` to test React itself. We must test React in both development and production modes. So, the solution is to fork `act` into separate public and internal implementations: - *public implementation of `act`* – exposed to users, only works in development mode, uses special runtime logic, does not support partial rendering - *internal implementation of `act`* – private, works in both development and productionm modes, only used by the React Core test suite, uses no special runtime logic, supports partial rendering (i.e. `toFlushAndYieldThrough`) The internal implementation should mostly match the public implementation's behavior, but since it's a private API, it doesn't have to match exactly. It works by mocking the test environment: it uses a mock build of Scheduler to flush rendering tasks, and Jest's mock timers to flush Suspense placeholders. --- .../ReactHooksInspectionIntegration-test.js | 2 +- .../__tests__/inspectedElementContext-test.js | 45 ++-- .../__tests__/storeComponentFilters-test.js | 2 +- .../src/__tests__/ReactDOMFiberAsync-test.js | 2 +- .../ReactDOMServerIntegrationHooks-test.js | 28 ++- ...DOMServerPartialHydration-test.internal.js | 2 +- ...MServerSelectiveHydration-test.internal.js | 4 +- .../ReactDOMServerSuspense-test.internal.js | 4 +- .../ReactDOMSuspensePlaceholder-test.js | 2 +- .../ReactErrorBoundaries-test.internal.js | 2 +- .../src/__tests__/ReactTestUtilsAct-test.js | 118 +++++---- ...ReactTestUtilsActUnmockedScheduler-test.js | 5 + ...tUnmockedSchedulerWarning-test.internal.js | 56 ----- .../ReactUnmockedSchedulerWarning-test.js | 170 +++++++++---- .../src/__tests__/ReactUpdates-test.js | 2 +- .../ReactDOMServerIntegrationTestUtils.js | 4 +- packages/react-dom/src/client/ReactDOM.js | 3 + .../DOMPluginEventSystem-test.internal.js | 10 +- .../__tests__/ChangeEventPlugin-test.js | 2 +- .../src/test-utils/ReactTestUtils.js | 5 +- .../src/test-utils/ReactTestUtilsAct.js | 235 ++++++------------ .../__tests__/useFocusWithin-test.internal.js | 2 +- .../src/createReactNoop.js | 121 ++++++++- .../src/ReactFiberReconciler.new.js | 1 - .../src/ReactFiberReconciler.old.js | 2 - .../src/ReactFiberWorkLoop.new.js | 176 ++++++------- .../src/ReactFiberWorkLoop.old.js | 176 ++++++------- .../src/SchedulerWithReactIntegration.new.js | 7 +- .../src/__tests__/ReactBlocks-test.js | 17 +- .../src/__tests__/ReactHooks-test.internal.js | 10 +- .../__tests__/ReactNoopRendererAct-test.js | 1 - .../__tests__/ReactSuspense-test.internal.js | 4 +- .../ReactSuspenseWithNoopRenderer-test.js | 41 ++- .../SchedulingProfiler-test.internal.js | 4 +- .../useMutableSourceHydration-test.js | 2 +- .../src/__tests__/ReactFresh-test.js | 4 +- .../__tests__/ReactFreshIntegration-test.js | 2 +- .../src/ReactTestRenderer.js | 119 +++++++++ .../__tests__/ReactTestRendererAct-test.js | 4 + .../ReactFlightDOMRelay-test.internal.js | 2 +- .../src/__tests__/ReactFlightDOM-test.js | 2 +- .../ReactDOMTracing-test.internal.js | 15 +- .../__tests__/ReactProfiler-test.internal.js | 39 +-- .../src/__tests__/useSubscription-test.js | 2 +- scripts/jest/shouldIgnoreConsoleError.js | 10 - 45 files changed, 822 insertions(+), 644 deletions(-) delete mode 100644 packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 01461dfc1648e..4dfe0bac0b1f1 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -22,7 +22,7 @@ describe('ReactHooksInspectionIntegration', () => { React = require('react'); ReactTestRenderer = require('react-test-renderer'); Scheduler = require('scheduler'); - act = ReactTestRenderer.act; + act = ReactTestRenderer.unstable_concurrentAct; ReactDebugTools = require('react-debug-tools'); }); diff --git a/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js b/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js index 29e8e379e4e43..a68de20ab0ee1 100644 --- a/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js +++ b/packages/react-devtools-shared/src/__tests__/inspectedElementContext-test.js @@ -33,6 +33,9 @@ describe('InspectedElementContext', () => { let TestUtils; let TreeContextController; + let TestUtilsAct; + let TestRendererAct; + beforeEach(() => { utils = require('./utils'); utils.beforeEachProfiling(); @@ -47,7 +50,9 @@ describe('InspectedElementContext', () => { ReactDOM = require('react-dom'); PropTypes = require('prop-types'); TestUtils = require('react-dom/test-utils'); + TestUtilsAct = TestUtils.unstable_concurrentAct; TestRenderer = utils.requireTestRenderer(); + TestRendererAct = TestUtils.unstable_concurrentAct; BridgeContext = require('react-devtools-shared/src/devtools/views/context') .BridgeContext; @@ -999,8 +1004,8 @@ describe('InspectedElementContext', () => { expect(inspectedElement).toMatchSnapshot('1: Initially inspect element'); inspectedElement = null; - TestUtils.act(() => { - TestRenderer.act(() => { + TestUtilsAct(() => { + TestRendererAct(() => { getInspectedElementPath(id, ['props', 'nestedObject', 'a']); jest.runOnlyPendingTimers(); }); @@ -1009,8 +1014,8 @@ describe('InspectedElementContext', () => { expect(inspectedElement).toMatchSnapshot('2: Inspect props.nestedObject.a'); inspectedElement = null; - TestUtils.act(() => { - TestRenderer.act(() => { + TestUtilsAct(() => { + TestRendererAct(() => { getInspectedElementPath(id, ['props', 'nestedObject', 'a', 'b', 'c']); jest.runOnlyPendingTimers(); }); @@ -1021,8 +1026,8 @@ describe('InspectedElementContext', () => { ); inspectedElement = null; - TestUtils.act(() => { - TestRenderer.act(() => { + TestUtilsAct(() => { + TestRendererAct(() => { getInspectedElementPath(id, [ 'props', 'nestedObject', @@ -1041,8 +1046,8 @@ describe('InspectedElementContext', () => { ); inspectedElement = null; - TestUtils.act(() => { - TestRenderer.act(() => { + TestUtilsAct(() => { + TestRendererAct(() => { getInspectedElementPath(id, ['hooks', 0, 'value']); jest.runOnlyPendingTimers(); }); @@ -1051,8 +1056,8 @@ describe('InspectedElementContext', () => { expect(inspectedElement).toMatchSnapshot('5: Inspect hooks.0.value'); inspectedElement = null; - TestUtils.act(() => { - TestRenderer.act(() => { + TestUtilsAct(() => { + TestRendererAct(() => { getInspectedElementPath(id, ['hooks', 0, 'value', 'foo', 'bar']); jest.runOnlyPendingTimers(); }); @@ -1108,8 +1113,8 @@ describe('InspectedElementContext', () => { expect(inspectedElement).toMatchSnapshot('1: Initially inspect element'); inspectedElement = null; - TestUtils.act(() => { - TestRenderer.act(() => { + TestUtilsAct(() => { + TestRendererAct(() => { getInspectedElementPath(id, ['props', 'set_of_sets', 0]); jest.runOnlyPendingTimers(); }); @@ -1179,7 +1184,7 @@ describe('InspectedElementContext', () => { expect(inspectedElement).toMatchSnapshot('1: Initially inspect element'); inspectedElement = null; - TestRenderer.act(() => { + TestRendererAct(() => { getInspectedElementPath(id, ['props', 'nestedObject', 'a']); jest.runOnlyPendingTimers(); }); @@ -1187,15 +1192,15 @@ describe('InspectedElementContext', () => { expect(inspectedElement).toMatchSnapshot('2: Inspect props.nestedObject.a'); inspectedElement = null; - TestRenderer.act(() => { + TestRendererAct(() => { getInspectedElementPath(id, ['props', 'nestedObject', 'c']); jest.runOnlyPendingTimers(); }); expect(inspectedElement).not.toBeNull(); expect(inspectedElement).toMatchSnapshot('3: Inspect props.nestedObject.c'); - TestRenderer.act(() => { - TestUtils.act(() => { + TestRendererAct(() => { + TestUtilsAct(() => { ReactDOM.render( { }); }); - TestRenderer.act(() => { + TestRendererAct(() => { inspectedElement = null; jest.advanceTimersByTime(1000); }); @@ -1281,7 +1286,7 @@ describe('InspectedElementContext', () => { expect(inspectedElement).not.toBeNull(); expect(inspectedElement).toMatchSnapshot('1: Initially inspect element'); - TestUtils.act(() => { + TestUtilsAct(() => { ReactDOM.render( { inspectedElement = null; - TestRenderer.act(() => { - TestUtils.act(() => { + TestRendererAct(() => { + TestUtilsAct(() => { getInspectedElementPath(id, ['props', 'nestedObject', 'a']); jest.runOnlyPendingTimers(); }); diff --git a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js index 341e06b04d57e..1a5be03f09aad 100644 --- a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js +++ b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js @@ -20,7 +20,7 @@ describe('Store component filters', () => { let utils; const act = (callback: Function) => { - TestUtils.act(() => { + TestUtils.unstable_concurrentAct(() => { callback(); }); jest.runAllTimers(); // Flush Bridge operations diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js index d6baddbe45c9e..5e783489bb932 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js @@ -28,7 +28,7 @@ describe('ReactDOMFiberAsync', () => { container = document.createElement('div'); React = require('react'); ReactDOM = require('react-dom'); - act = require('react-dom/test-utils').act; + act = require('react-dom/test-utils').unstable_concurrentAct; Scheduler = require('scheduler'); document.body.appendChild(container); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js index a66a86866776f..6cb57a8560b73 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js @@ -17,6 +17,7 @@ let React; let ReactDOM; let ReactDOMServer; let ReactTestUtils; +let act; let Scheduler; let useState; let useReducer; @@ -43,6 +44,7 @@ function initModules() { ReactDOMServer = require('react-dom/server'); ReactTestUtils = require('react-dom/test-utils'); Scheduler = require('scheduler'); + act = ReactTestUtils.unstable_concurrentAct; useState = React.useState; useReducer = React.useReducer; useEffect = React.useEffect; @@ -1063,7 +1065,7 @@ describe('ReactDOMServerHooks', () => { expect(domNode.children.length).toEqual(1); expect(oldClientId).not.toBeNull(); - await ReactTestUtils.act(async () => _setShowId(true)); + await act(async () => _setShowId(true)); expect(domNode.children.length).toEqual(2); expect(domNode.children[0].getAttribute('aria-labelledby')).toEqual( @@ -1281,7 +1283,7 @@ describe('ReactDOMServerHooks', () => { const oldServerId = container.children[0].children[0].getAttribute('id'); expect(oldServerId).not.toBeNull(); - await ReactTestUtils.act(async () => { + await act(async () => { _setShowDiv(true); }); expect(container.children[0].children.length).toEqual(2); @@ -1322,7 +1324,7 @@ describe('ReactDOMServerHooks', () => { const oldServerId = container.children[0].children[0].getAttribute('id'); expect(oldServerId).not.toBeNull(); - await ReactTestUtils.act(async () => { + await act(async () => { _setShowDiv(true); }); expect(container.children[0].children.length).toEqual(2); @@ -1356,12 +1358,12 @@ describe('ReactDOMServerHooks', () => { document.body.append(container); container.innerHTML = ReactDOMServer.renderToString(); const root = ReactDOM.unstable_createRoot(container, {hydrate: true}); - ReactTestUtils.act(() => { + act(() => { root.render(); }); expect(Scheduler).toHaveYielded(['App', 'App']); // The ID goes from not being used to being added to the page - ReactTestUtils.act(() => { + act(() => { _setShow(true); }); expect(Scheduler).toHaveYielded(['App', 'App']); @@ -1391,7 +1393,7 @@ describe('ReactDOMServerHooks', () => { ReactDOM.hydrate(, container); expect(Scheduler).toHaveYielded(['App', 'App']); // The ID goes from not being used to being added to the page - ReactTestUtils.act(() => { + act(() => { _setShow(true); }); expect(Scheduler).toHaveYielded(['App']); @@ -1418,12 +1420,12 @@ describe('ReactDOMServerHooks', () => { document.body.append(container); container.innerHTML = ReactDOMServer.renderToString(); const root = ReactDOM.unstable_createRoot(container, {hydrate: true}); - ReactTestUtils.act(() => { + act(() => { root.render(); }); // The ID goes from not being used to being added to the page - ReactTestUtils.act(() => { + act(() => { ReactDOM.flushSync(() => { _setShow(true); }); @@ -1518,7 +1520,7 @@ describe('ReactDOMServerHooks', () => { expect(child1Ref.current).toBe(null); expect(Scheduler).toHaveYielded([]); - ReactTestUtils.act(() => { + act(() => { _setShow(true); // State update should trigger the ID to update, which changes the props @@ -1603,7 +1605,7 @@ describe('ReactDOMServerHooks', () => { suspend = true; const root = ReactDOM.unstable_createRoot(container, {hydrate: true}); - await ReactTestUtils.act(async () => { + await act(async () => { root.render(); }); jest.runAllTimers(); @@ -1616,7 +1618,7 @@ describe('ReactDOMServerHooks', () => { container.children[0].children[0].getAttribute('id'), ).not.toBeNull(); - await ReactTestUtils.act(async () => { + await act(async () => { suspend = false; resolve(); await promise; @@ -1703,7 +1705,7 @@ describe('ReactDOMServerHooks', () => { suspend = false; const root = ReactDOM.unstable_createRoot(container, {hydrate: true}); - await ReactTestUtils.act(async () => { + await act(async () => { root.render(); }); jest.runAllTimers(); @@ -1968,7 +1970,7 @@ describe('ReactDOMServerHooks', () => { expect(Scheduler).toHaveYielded([]); expect(Scheduler).toFlushAndYield([]); - ReactTestUtils.act(() => { + act(() => { _setShow(false); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js index 1e6a912651832..9a897789b47ca 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js @@ -79,7 +79,7 @@ describe('ReactDOMServerPartialHydration', () => { React = require('react'); ReactDOM = require('react-dom'); - act = require('react-dom/test-utils').act; + act = require('react-dom/test-utils').unstable_concurrentAct; ReactDOMServer = require('react-dom/server'); Scheduler = require('scheduler'); Suspense = React.Suspense; diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js index 3a948a0651b1d..6b543805ea102 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js @@ -17,6 +17,7 @@ let ReactDOMServer; let ReactTestUtils; let Scheduler; let Suspense; +let act; function dispatchMouseHoverEvent(to, from) { if (!to) { @@ -101,6 +102,7 @@ describe('ReactDOMServerSelectiveHydration', () => { ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); ReactTestUtils = require('react-dom/test-utils'); + act = ReactTestUtils.unstable_concurrentAct; Scheduler = require('scheduler'); Suspense = React.Suspense; }); @@ -880,7 +882,7 @@ describe('ReactDOMServerSelectiveHydration', () => { const spanC = container.getElementsByTagName('span')[4]; const root = ReactDOM.createRoot(container, {hydrate: true}); - ReactTestUtils.act(() => { + act(() => { root.render(); // Hydrate the shell. diff --git a/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js index cc3b563d0ff43..6c48b36bd107d 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js @@ -15,6 +15,7 @@ let React; let ReactDOM; let ReactDOMServer; let ReactTestUtils; +let act; function initModules() { // Reset warning cache. @@ -24,6 +25,7 @@ function initModules() { ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); ReactTestUtils = require('react-dom/test-utils'); + act = ReactTestUtils.unstable_concurrentAct; // Make them available to the helpers. return { @@ -124,7 +126,7 @@ describe('ReactDOMServerSuspense', () => { expect(divB.tagName).toBe('DIV'); expect(divB.textContent).toBe('B'); - ReactTestUtils.act(() => { + act(() => { const root = ReactDOM.createBlockingRoot(parent, {hydrate: true}); root.render(example); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js b/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js index 02da1e84b202c..44f6aeebe6711 100644 --- a/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMSuspensePlaceholder-test.js @@ -28,7 +28,7 @@ describe('ReactDOMSuspensePlaceholder', () => { ReactCache = require('react-cache'); ReactTestUtils = require('react-dom/test-utils'); Scheduler = require('scheduler'); - act = ReactTestUtils.act; + act = ReactTestUtils.unstable_concurrentAct; Suspense = React.Suspense; container = document.createElement('div'); document.body.appendChild(container); diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js index 204c67ba52846..46ace25fd7a16 100644 --- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js @@ -44,7 +44,7 @@ describe('ReactErrorBoundaries', () => { ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; ReactDOM = require('react-dom'); React = require('react'); - act = require('react-dom/test-utils').act; + act = require('react-dom/test-utils').unstable_concurrentAct; Scheduler = require('scheduler'); BrokenConstructor = class extends React.Component { diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js index 7b48d0118bebd..8be08aee8f404 100644 --- a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js +++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js @@ -14,6 +14,7 @@ let SchedulerTracing; let Scheduler; let act; let container; +let log; jest.useRealTimers(); @@ -25,6 +26,12 @@ function sleep(period) { }); } +function flushLog() { + const prevLog = log; + log = []; + return prevLog; +} + describe('ReactTestUtils.act()', () => { // first we run all the tests with concurrent mode if (__EXPERIMENTAL__) { @@ -33,18 +40,15 @@ describe('ReactTestUtils.act()', () => { concurrentRoot = ReactDOM.unstable_createRoot(dom); concurrentRoot.render(el); }; - const unmountConcurrent = _dom => { if (concurrentRoot !== null) { concurrentRoot.unmount(); concurrentRoot = null; } }; - const rerenderConcurrent = el => { concurrentRoot.render(el); }; - runActTests( 'concurrent mode', renderConcurrent, @@ -79,18 +83,15 @@ describe('ReactTestUtils.act()', () => { blockingRoot = ReactDOM.unstable_createBlockingRoot(dom); blockingRoot.render(el); }; - const unmountBatched = dom => { if (blockingRoot !== null) { blockingRoot.unmount(); blockingRoot = null; } }; - const rerenderBatched = el => { blockingRoot.render(el); }; - runActTests( 'blocking mode', renderBatched, @@ -125,6 +126,8 @@ describe('ReactTestUtils.act()', () => { }); // @gate experimental + // @gate FIXME + // @gate __DEV__ it('warns in blocking mode', () => { expect(() => { const root = ReactDOM.unstable_createBlockingRoot( @@ -138,6 +141,8 @@ describe('ReactTestUtils.act()', () => { }); // @gate experimental + // @gate FIXME + // @gate __DEV__ it('warns in concurrent mode', () => { expect(() => { const root = ReactDOM.unstable_createRoot( @@ -156,6 +161,7 @@ function runActTests(label, render, unmount, rerender) { describe(label, () => { beforeEach(() => { jest.resetModules(); + jest.unmock('scheduler'); React = require('react'); ReactDOM = require('react-dom'); ReactTestUtils = require('react-dom/test-utils'); @@ -164,6 +170,8 @@ function runActTests(label, render, unmount, rerender) { act = ReactTestUtils.act; container = document.createElement('div'); document.body.appendChild(container); + + log = []; }); afterEach(() => { @@ -172,10 +180,11 @@ function runActTests(label, render, unmount, rerender) { }); describe('sync', () => { + // @gate __DEV__ it('can use act to flush effects', () => { function App() { React.useEffect(() => { - Scheduler.unstable_yieldValue(100); + log.push(100); }); return null; } @@ -184,14 +193,15 @@ function runActTests(label, render, unmount, rerender) { render(, container); }); - expect(Scheduler).toHaveYielded([100]); + expect(flushLog()).toEqual([100]); }); + // @gate __DEV__ it('flushes effects on every call', () => { function App() { const [ctr, setCtr] = React.useState(0); React.useEffect(() => { - Scheduler.unstable_yieldValue(ctr); + log.push(ctr); }); return (