mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-8847] Delay browser local storage operations during reseed (#9536)
* Define matchers to test promise fulfillment These are useful for validating that promises depend on other events prior to fulfilling. * Expose custom matchers to jest projects Team-specific projects are not touched here to try and reduce review burden. * Block browser local operations awaiting reseed This should closes a narrow race condition resulting from storage operations during a reseed event. * Import from barrel file This might fix the failing test, but I'm not sure _why_ * Document helper methods * Validate as few properties as possible per test * Simplify expected value representation * Allow waiting in promise matchers * Specify resolution times in promise orchestration tests. * Test behavior triggering multiple reseeds. * Fix typo * Avoid testing implementation details * Clear reseed on startup in case a previous process was aborted in the middle of a reseed. * Correct formatting
This commit is contained in:
@@ -1 +1,57 @@
|
||||
import { toBeFulfilled, toBeResolved, toBeRejected } from "./promise-fulfilled";
|
||||
import { toAlmostEqual } from "./to-almost-equal";
|
||||
import { toEqualBuffer } from "./to-equal-buffer";
|
||||
|
||||
export * from "./to-equal-buffer";
|
||||
export * from "./to-almost-equal";
|
||||
export * from "./promise-fulfilled";
|
||||
|
||||
export function addCustomMatchers() {
|
||||
expect.extend({
|
||||
toEqualBuffer: toEqualBuffer,
|
||||
toAlmostEqual: toAlmostEqual,
|
||||
toBeFulfilled: toBeFulfilled,
|
||||
toBeResolved: toBeResolved,
|
||||
toBeRejected: toBeRejected,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CustomMatchers<R = unknown> {
|
||||
toEqualBuffer(expected: Uint8Array | ArrayBuffer): R;
|
||||
/**
|
||||
* Matches the expected date within an optional ms precision
|
||||
* @param expected The expected date
|
||||
* @param msPrecision The optional precision in milliseconds
|
||||
*/
|
||||
toAlmostEqual(expected: Date, msPrecision?: number): R;
|
||||
/**
|
||||
* Matches whether the received promise has been fulfilled.
|
||||
*
|
||||
* Failure if the promise is not currently fulfilled.
|
||||
*
|
||||
* @param received The promise to test
|
||||
* @param withinMs The time within the promise should be fulfilled. Defaults to 0, indicating that the promise should already be fulfilled
|
||||
* @returns CustomMatcherResult indicating whether or not the test passed
|
||||
*/
|
||||
toBeFulfilled(withinMs?: number): Promise<R>;
|
||||
/**
|
||||
* Matches whether the received promise has been resolved.
|
||||
*
|
||||
* Failure if the promise is not currently fulfilled or if it has been rejected.
|
||||
*
|
||||
* @param received The promise to test
|
||||
* @param withinMs The time within the promise should be resolved. Defaults to 0, indicating that the promise should already be resolved
|
||||
* @returns CustomMatcherResult indicating whether or not the test passed
|
||||
*/
|
||||
toBeResolved(withinMs?: number): Promise<R>;
|
||||
/**
|
||||
* Matches whether the received promise has been rejected.
|
||||
*
|
||||
* Failure if the promise is not currently fulfilled or if it has been resolved, but not rejected.
|
||||
*
|
||||
* @param received The promise to test
|
||||
* @param withinMs The time within the promise should be rejected. Defaults to 0, indicating that the promise should already be rejected
|
||||
* @returns CustomMatcherResult indicating whether or not the test passed
|
||||
*/
|
||||
toBeRejected(withinMs?: number): Promise<R>;
|
||||
}
|
||||
|
||||
86
libs/common/spec/matchers/promise-fulfilled.spec.ts
Normal file
86
libs/common/spec/matchers/promise-fulfilled.spec.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
describe("toBeFulfilled", () => {
|
||||
it("passes when promise is resolved", async () => {
|
||||
const promise = Promise.resolve("resolved");
|
||||
await promise;
|
||||
await expect(promise).toBeFulfilled();
|
||||
});
|
||||
|
||||
it("passes when promise is rejected", async () => {
|
||||
const promise = Promise.reject("rejected");
|
||||
await promise.catch(() => {});
|
||||
await expect(promise).toBeFulfilled();
|
||||
});
|
||||
|
||||
it("fails when promise is pending", async () => {
|
||||
const promise = new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
await expect(promise).not.toBeFulfilled();
|
||||
});
|
||||
|
||||
it("passes when the promise is fulfilled within the given time limit", async () => {
|
||||
const promise = new Promise((resolve) => setTimeout(resolve, 1));
|
||||
await expect(promise).toBeFulfilled(2);
|
||||
});
|
||||
|
||||
it("passes when the promise is not fulfilled within the given time limit", async () => {
|
||||
const promise = new Promise((resolve) => setTimeout(resolve, 2));
|
||||
await expect(promise).not.toBeFulfilled(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toBeResolved", () => {
|
||||
it("passes when promise is resolved", async () => {
|
||||
const promise = Promise.resolve("resolved");
|
||||
await promise;
|
||||
await expect(promise).toBeResolved();
|
||||
});
|
||||
|
||||
it("fails when promise is rejected", async () => {
|
||||
const promise = Promise.reject("rejected");
|
||||
await promise.catch(() => {});
|
||||
await expect(promise).not.toBeResolved();
|
||||
});
|
||||
|
||||
it("fails when promise is pending", async () => {
|
||||
const promise = new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
await expect(promise).not.toBeResolved();
|
||||
});
|
||||
|
||||
it("passes when the promise is resolved within the given time limit", async () => {
|
||||
const promise = new Promise((resolve) => setTimeout(resolve, 1));
|
||||
await expect(promise).toBeResolved(2);
|
||||
});
|
||||
|
||||
it("passes when the promise is not resolved within the given time limit", async () => {
|
||||
const promise = new Promise((resolve) => setTimeout(resolve, 2));
|
||||
await expect(promise).not.toBeResolved(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toBeRejected", () => {
|
||||
it("fails when promise is resolved", async () => {
|
||||
const promise = Promise.resolve("resolved");
|
||||
await promise;
|
||||
await expect(promise).not.toBeRejected();
|
||||
});
|
||||
|
||||
it("passes when promise is rejected", async () => {
|
||||
const promise = Promise.reject("rejected");
|
||||
await promise.catch(() => {});
|
||||
await expect(promise).toBeRejected();
|
||||
});
|
||||
|
||||
it("fails when promise is pending", async () => {
|
||||
const promise = new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
await expect(promise).not.toBeRejected();
|
||||
});
|
||||
|
||||
it("passes when the promise is resolved within the given time limit", async () => {
|
||||
const promise = new Promise((_, reject) => setTimeout(reject, 1));
|
||||
await expect(promise).toBeFulfilled(2);
|
||||
});
|
||||
|
||||
it("passes when the promise is not resolved within the given time limit", async () => {
|
||||
const promise = new Promise((_, reject) => setTimeout(reject, 2));
|
||||
await expect(promise).not.toBeFulfilled(1);
|
||||
});
|
||||
});
|
||||
79
libs/common/spec/matchers/promise-fulfilled.ts
Normal file
79
libs/common/spec/matchers/promise-fulfilled.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
async function wait(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches whether the received promise has been fulfilled.
|
||||
*
|
||||
* Failure if the promise is not currently fulfilled.
|
||||
*
|
||||
* @param received The promise to test
|
||||
* @param withinMs The time within the promise should be fulfilled. Defaults to 0, indicating that the promise should already be fulfilled
|
||||
* @returns CustomMatcherResult indicating whether or not the test passed
|
||||
*/
|
||||
export const toBeFulfilled: jest.CustomMatcher = async function (
|
||||
received: Promise<unknown>,
|
||||
withinMs = 0,
|
||||
) {
|
||||
return {
|
||||
pass: await Promise.race([
|
||||
wait(withinMs).then(() => false),
|
||||
received.then(
|
||||
() => true,
|
||||
() => true,
|
||||
),
|
||||
]),
|
||||
message: () => `expected promise to be fulfilled`,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches whether the received promise has been resolved.
|
||||
*
|
||||
* Failure if the promise is not currently fulfilled or if it has been rejected.
|
||||
*
|
||||
* @param received The promise to test
|
||||
* @param withinMs The time within the promise should be resolved. Defaults to 0, indicating that the promise should already be resolved
|
||||
* @returns CustomMatcherResult indicating whether or not the test passed
|
||||
|
||||
*/
|
||||
export const toBeResolved: jest.CustomMatcher = async function (
|
||||
received: Promise<unknown>,
|
||||
withinMs = 0,
|
||||
) {
|
||||
return {
|
||||
pass: await Promise.race([
|
||||
wait(withinMs).then(() => false),
|
||||
received.then(
|
||||
() => true,
|
||||
() => false,
|
||||
),
|
||||
]),
|
||||
message: () => `expected promise to be resolved`,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Matches whether the received promise has been rejected.
|
||||
*
|
||||
* Failure if the promise is not currently fulfilled or if it has been resolved, but not rejected.
|
||||
*
|
||||
* @param received The promise to test
|
||||
* @param withinMs The time within the promise should be rejected. Defaults to 0, indicating that the promise should already be rejected
|
||||
* @returns CustomMatcherResult indicating whether or not the test passed
|
||||
*/
|
||||
export const toBeRejected: jest.CustomMatcher = async function (
|
||||
received: Promise<unknown>,
|
||||
withinMs = 0,
|
||||
) {
|
||||
return {
|
||||
pass: await Promise.race([
|
||||
wait(withinMs).then(() => false),
|
||||
received.then(
|
||||
() => false,
|
||||
() => true,
|
||||
),
|
||||
]),
|
||||
message: () => `expected promise to be rejected`,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user