@testduet/wait-for
Version:
Loop a function and spin-wait until it return or resolve.
1 lines • 16.5 kB
Source Map (JSON)
{"version":3,"sources":["../src/private/config.ts","../src/private/helpers.ts","../src/private/waitFor.ts"],"sourcesContent":["/*!\n * The MIT License (MIT)\n * Copyright (c) 2017 Kent C. Dodds\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n// Adopted from https://github.com/testing-library/dom-testing-library/blob/a86c54ccda5242ad8dfc1c70d31980bdbf96af7f/src/config.ts\n\nimport { Config } from './types/config.ts';\n\n// It would be cleaner for this to live inside './queries', but\n// other parts of the code assume that all exports from\n// './queries' are query functions.\nconst config: Config = {\n asyncUtilTimeout: 1000,\n // asyncWrapper and advanceTimersWrapper is to support React's async `act` function.\n // forcing react-testing-library to wrap all async functions would've been\n // a total nightmare (consider wrapping every findBy* query and then also\n // updating `within` so those would be wrapped too. Total nightmare).\n // so we have this config option that's really only intended for\n // react-testing-library to use. For that reason, this feature will remain\n // undocumented.\n asyncWrapper: cb => cb(),\n unstable_advanceTimersWrapper: cb => cb(),\n showOriginalStackTrace: false\n};\n\nexport function getConfig(): Config {\n return config;\n}\n","/*!\n * The MIT License (MIT)\n * Copyright (c) 2017 Kent C. Dodds\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n// Adopted from https://github.com/testing-library/dom-testing-library/blob/a86c54ccda5242ad8dfc1c70d31980bdbf96af7f/src/helpers.ts\n\n/// <reference types=\"jest\" />\n\nfunction jestFakeTimersAreEnabled(): boolean {\n /* istanbul ignore else */\n // eslint-disable-next-line\n if (typeof jest !== 'undefined' && jest !== null) {\n return (\n // legacy timers\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (setTimeout as any)._isMockFunction === true ||\n // modern timers\n // eslint-disable-next-line prefer-object-has-own -- not supported by our support matrix\n Object.prototype.hasOwnProperty.call(setTimeout, 'clock')\n );\n }\n // istanbul ignore next\n return false;\n}\nexport { jestFakeTimersAreEnabled };\n","/*!\n * The MIT License (MIT)\n * Copyright (c) 2017 Kent C. Dodds\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n// Adopted from https://github.com/testing-library/dom-testing-library/blob/a86c54ccda5242ad8dfc1c70d31980bdbf96af7f/src/wait-for.js\n\n/// <reference types=\"jest\" />\n\nimport { getConfig } from './config.ts';\nimport {\n // We import these from the helpers rather than using the global version\n // because these will be *real* timers, regardless of whether we're in\n // an environment that's faked the timers out.\n jestFakeTimersAreEnabled\n} from './helpers.ts';\n\n// This is so the stack trace the developer sees is one that's\n// closer to their code (because async stack traces are hard to follow).\nfunction copyStackTrace(target: unknown, source: unknown): void {\n if (\n target &&\n typeof target === 'object' &&\n 'stack' in target &&\n 'message' in target &&\n typeof target.message === 'string' &&\n source &&\n typeof source === 'object' &&\n 'message' in source &&\n typeof source.message === 'string' &&\n 'stack' in source &&\n typeof source.stack === 'string'\n ) {\n target.stack = source.stack.replace(source.message, target.message);\n }\n}\n\ntype WaitForInit = {\n timeout?: number | undefined;\n showOriginalStackTrace?: boolean | undefined;\n stackTraceError?: unknown | undefined;\n interval?: number | undefined;\n onTimeout?: ((error: unknown) => unknown) | undefined;\n};\n\nfunction waitFor<T>(\n callback: () => T,\n {\n timeout = getConfig().asyncUtilTimeout,\n showOriginalStackTrace = getConfig().showOriginalStackTrace,\n stackTraceError,\n interval = 50,\n onTimeout = error => error\n }: WaitForInit\n): Promise<T> {\n if (typeof callback !== 'function') {\n throw new TypeError('Received `callback` arg must be a function');\n }\n\n // eslint-disable-next-line no-async-promise-executor\n return new Promise(async (resolve, reject) => {\n let lastError: unknown;\n let intervalId: number;\n let finished = false;\n let promiseStatus = 'idle';\n\n const overallTimeoutTimer = setTimeout(handleTimeout, timeout);\n\n const usingJestFakeTimers = jestFakeTimersAreEnabled();\n if (usingJestFakeTimers) {\n const { unstable_advanceTimersWrapper: advanceTimersWrapper } = getConfig();\n checkCallback();\n // this is a dangerous rule to disable because it could lead to an\n // infinite loop. However, eslint isn't smart enough to know that we're\n // setting finished inside `onDone` which will be called when we're done\n // waiting or when we've timed out.\n // eslint-disable-next-line no-unmodified-loop-condition\n while (!finished) {\n if (!jestFakeTimersAreEnabled()) {\n const error = new Error(\n `Changed from using fake timers to real timers while using waitFor. This is not allowed and will result in very strange behavior. Please ensure you're awaiting all async things your test is doing before changing to real timers. For more info, please go to https://github.com/testing-library/dom-testing-library/issues/830`\n );\n if (!showOriginalStackTrace) copyStackTrace(error, stackTraceError);\n reject(error);\n return;\n }\n\n // In this rare case, we *need* to wait for in-flight promises\n // to resolve before continuing. We don't need to take advantage\n // of parallelization so we're fine.\n // https://stackoverflow.com/a/59243586/971592\n // eslint-disable-next-line no-await-in-loop\n await advanceTimersWrapper(async () => {\n // we *could* (maybe should?) use `advanceTimersToNextTimer` but it's\n // possible that could make this loop go on forever if someone is using\n // third party code that's setting up recursive timers so rapidly that\n // the user's timer's don't get a chance to resolve. So we'll advance\n // by an interval instead. (We have a test for this case).\n\n if (\n 'clock' in setTimeout &&\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n typeof (setTimeout as any).clock === 'object' &&\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n 'tick' in (setTimeout as any).clock &&\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n typeof (setTimeout as any).clock.tick === 'function'\n ) {\n // Added support for fake timers which added a setTimeout.clock.tick function, such as Sinon.JS and Jest.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (setTimeout as any).clock.tick(interval);\n } else {\n // Jest use Sinon.JS, but if we call jest.advanceTimersByTime with Sinon.JS fake timers, it will console.error().\n // So we prefer setTimeout.clock.tick before jest.advanceTimersByTime.\n jest.advanceTimersByTime(interval);\n }\n });\n\n // Could have timed-out\n if (finished) {\n break;\n }\n // It's really important that checkCallback is run *before* we flush\n // in-flight promises. To be honest, I'm not sure why, and I can't quite\n // think of a way to reproduce the problem in a test, but I spent\n // an entire day banging my head against a wall on this.\n checkCallback();\n }\n } else {\n intervalId = setInterval(checkRealTimersCallback, interval);\n checkCallback();\n }\n\n function onDone(error: unknown, result: null): void;\n function onDone(error: null, result: T): void;\n function onDone(error: unknown | null, result: T | null): void {\n finished = true;\n clearTimeout(overallTimeoutTimer);\n\n if (!usingJestFakeTimers) {\n clearInterval(intervalId);\n }\n\n if (error) {\n reject(error);\n } else {\n resolve(result as T);\n }\n }\n\n function checkRealTimersCallback() {\n if (jestFakeTimersAreEnabled()) {\n const error = new Error(\n `Changed from using real timers to fake timers while using waitFor. This is not allowed and will result in very strange behavior. Please ensure you're awaiting all async things your test is doing before changing to fake timers. For more info, please go to https://github.com/testing-library/dom-testing-library/issues/830`\n );\n if (!showOriginalStackTrace) copyStackTrace(error, stackTraceError);\n return reject(error);\n } else {\n return checkCallback();\n }\n }\n\n function checkCallback() {\n if (promiseStatus === 'pending') return;\n try {\n const result = callback();\n if (result && typeof result === 'object' && 'then' in result && typeof result?.then === 'function') {\n promiseStatus = 'pending';\n (result as PromiseLike<T>).then(\n resolvedValue => {\n promiseStatus = 'resolved';\n onDone(null, resolvedValue);\n },\n rejectedValue => {\n promiseStatus = 'rejected';\n lastError = rejectedValue;\n }\n );\n } else {\n onDone(null, result);\n }\n // If `callback` throws, wait for the next mutation, interval, or timeout.\n } catch (error) {\n // Save the most recent callback error to reject the promise with it in the event of a timeout\n lastError = error;\n }\n }\n\n function handleTimeout() {\n let error: unknown;\n if (lastError) {\n error = lastError;\n } else {\n error = new Error('Timed out in waitFor.');\n if (!showOriginalStackTrace) {\n copyStackTrace(error, stackTraceError);\n }\n }\n onDone(onTimeout(error), null);\n }\n });\n}\n\nfunction waitForWrapper<T>(callback: () => T, options?: undefined | WaitForInit) {\n // create the error here so its stack trace is as close to the\n // calling code as possible\n const stackTraceError = new Error('STACK_TRACE_MESSAGE');\n return getConfig().asyncWrapper(() => waitFor(callback, { stackTraceError, ...options }));\n}\n\nexport { waitForWrapper as waitFor, type WaitForInit };\n\n/*\neslint\n max-lines-per-function: [\"error\", {\"max\": 200}],\n*/\n"],"mappings":"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","names":[]}