From 6277ef53252f348eebf431a8180ea8db4c0ca3b3 Mon Sep 17 00:00:00 2001 From: Nicolas Mattia Date: Fri, 10 Nov 2023 15:49:16 +0100 Subject: [PATCH] Refactor storage tests (#2031) This improves the storage tests in several ways: * The local storage helper does _not_ wrap the whole test anymore; this caused issues because e.g. individual tests would not be singled out or skipped with test.only() or test.skip(). * The `after` parameter of the local storage helper can now also be a validating function on the storage content, making it possible to assert specific properties (for instead assert that an anchor is set without need to know the exact timestamp it was last used). * Some comment about local storage are made a bit more general. --- src/frontend/src/storage/index.test.ts | 235 +++++++++++++++---------- 1 file changed, 140 insertions(+), 95 deletions(-) diff --git a/src/frontend/src/storage/index.test.ts b/src/frontend/src/storage/index.test.ts index ebd94675ae..629ed98b08 100644 --- a/src/frontend/src/storage/index.test.ts +++ b/src/frontend/src/storage/index.test.ts @@ -1,118 +1,158 @@ import { nonNullish } from "@dfinity/utils"; -import { vi } from "vitest"; -import { getAnchors, MAX_SAVED_ANCHORS, setAnchorUsed } from "."; +import { MAX_SAVED_ANCHORS, getAnchors, setAnchorUsed } from "."; -testStorage("anchors default to nothing", () => { +test("anchors default to nothing", () => { expect(getAnchors()).toStrictEqual([]); }); -testStorage( +test( "old userNumber is recovered", - () => { - expect(getAnchors()).toStrictEqual([BigInt(123456)]); - }, - { localStorage: { before: { userNumber: "123456" } } } + withStorage( + () => { + expect(getAnchors()).toStrictEqual([BigInt(123456)]); + }, + { localStorage: { before: { userNumber: "123456" } } } + ) ); -testStorage( - "reading old userNumber migrates anchors", - () => { - vi.useFakeTimers().setSystemTime(new Date(20)); - getAnchors(); - vi.useRealTimers(); - }, - { - localStorage: { - before: { userNumber: "123456" }, - after: { - anchors: JSON.stringify({ - "123456": { lastUsedTimestamp: 20 }, - }), - userNumber: "123456", - }, +test( + "old userNumber is not deleted", + withStorage( + () => { + getAnchors(); }, - } + { + localStorage: { + before: { userNumber: "123456" }, + after: (storage) => { + expect(storage["userNumber"]).toBe("123456"); + }, + }, + } + ) ); -testStorage("one anchor can be stored", () => { - setAnchorUsed(BigInt(10000)); - expect(getAnchors()).toStrictEqual([BigInt(10000)]); -}); +test( + "reading old userNumber migrates anchors", + withStorage( + () => { + getAnchors(); + }, + { + localStorage: { + before: { userNumber: "123456" }, + after: (storage) => { + const value = storage["anchors"]; + expect(value).toBeDefined(); + const anchors = JSON.parse(value); + expect(anchors).toBeTypeOf("object"); + expect(anchors["123456"]).toBeDefined(); + }, + }, + } + ) +); -testStorage("multiple anchors can be stored", () => { - setAnchorUsed(BigInt(10000)); - setAnchorUsed(BigInt(10001)); - setAnchorUsed(BigInt(10003)); - expect(getAnchors()).toContain(BigInt(10000)); - expect(getAnchors()).toContain(BigInt(10001)); - expect(getAnchors()).toContain(BigInt(10003)); -}); +test( + "one anchor can be stored", + withStorage(() => { + setAnchorUsed(BigInt(10000)); + expect(getAnchors()).toStrictEqual([BigInt(10000)]); + }) +); -testStorage("anchors are sorted", () => { - const anchors = [BigInt(10400), BigInt(10001), BigInt(1011003)]; - for (const anchor of anchors) { - setAnchorUsed(anchor); - } - anchors.sort(); - expect(getAnchors()).toStrictEqual(anchors); -}); +test( + "multiple anchors can be stored", + withStorage(() => { + setAnchorUsed(BigInt(10000)); + setAnchorUsed(BigInt(10001)); + setAnchorUsed(BigInt(10003)); + expect(getAnchors()).toContain(BigInt(10000)); + expect(getAnchors()).toContain(BigInt(10001)); + expect(getAnchors()).toContain(BigInt(10003)); + }) +); -testStorage("only N anchors are stored", () => { - for (let i = 0; i < MAX_SAVED_ANCHORS + 5; i++) { - setAnchorUsed(BigInt(i)); - } - expect(getAnchors().length).toStrictEqual(MAX_SAVED_ANCHORS); -}); +test( + "anchors are sorted", + withStorage(() => { + const anchors = [BigInt(10400), BigInt(10001), BigInt(1011003)]; + for (const anchor of anchors) { + setAnchorUsed(anchor); + } + anchors.sort(); + expect(getAnchors()).toStrictEqual(anchors); + }) +); -testStorage("old anchors are dropped", () => { - vi.useFakeTimers().setSystemTime(new Date(0)); - setAnchorUsed(BigInt(10000)); - vi.useFakeTimers().setSystemTime(new Date(1)); - setAnchorUsed(BigInt(203000)); - vi.useFakeTimers().setSystemTime(new Date(2)); - for (let i = 0; i < MAX_SAVED_ANCHORS; i++) { - setAnchorUsed(BigInt(i)); - } - expect(getAnchors()).not.toContain(BigInt(10000)); - expect(getAnchors()).not.toContain(BigInt(203000)); - vi.useRealTimers(); -}); +test( + "only N anchors are stored", + withStorage(() => { + for (let i = 0; i < MAX_SAVED_ANCHORS + 5; i++) { + setAnchorUsed(BigInt(i)); + } + expect(getAnchors().length).toStrictEqual(MAX_SAVED_ANCHORS); + }) +); -testStorage( - "unknown fields are not dropped", - () => { - vi.useFakeTimers().setSystemTime(new Date(20)); +test( + "old anchors are dropped", + withStorage(() => { + vi.useFakeTimers().setSystemTime(new Date(0)); setAnchorUsed(BigInt(10000)); + vi.useFakeTimers().setSystemTime(new Date(1)); + setAnchorUsed(BigInt(203000)); + vi.useFakeTimers().setSystemTime(new Date(2)); + for (let i = 0; i < MAX_SAVED_ANCHORS; i++) { + setAnchorUsed(BigInt(i)); + } + expect(getAnchors()).not.toContain(BigInt(10000)); + expect(getAnchors()).not.toContain(BigInt(203000)); vi.useRealTimers(); - }, - { - localStorage: { - before: { - anchors: JSON.stringify({ - "10000": { lastUsedTimestamp: 10, hello: "world" }, - }), - }, - after: { - anchors: JSON.stringify({ - "10000": { lastUsedTimestamp: 20, hello: "world" }, - }), - }, + }) +); + +test( + "unknown fields are not dropped", + withStorage( + () => { + vi.useFakeTimers().setSystemTime(new Date(20)); + setAnchorUsed(BigInt(10000)); + vi.useRealTimers(); }, - } + { + localStorage: { + before: { + anchors: JSON.stringify({ + "10000": { lastUsedTimestamp: 10, hello: "world" }, + }), + }, + after: { + anchors: JSON.stringify({ + "10000": { lastUsedTimestamp: 20, hello: "world" }, + }), + }, + }, + } + ) ); -/** Run a test that makes use of localStorage. Local storage is always cleared after - * the test was run. - * If `before` is specified, local storage is populated with its content before the test is run. - * If `after` is specified, the content of local storage are checked against `after` after the - * test is run and before local storage is cleared. +/** Test storage usage. Storage is cleared after the callback has returned. + * If `before` is specified, storage is populated with its content before the test is run. + * If `after` is specified, the content of storage are checked against `after` after the + * test is run and before storage is cleared. + * If `after` is a function, the function is called with the content of the storage. */ -function testStorage( - name: string, +function withStorage( fn: () => void, - opts?: { localStorage?: { before?: LocalStorage; after?: LocalStorage } } -) { - test(name, () => { + opts?: { + localStorage?: { + before?: LocalStorage; + after?: LocalStorage | ((storage: LocalStorage) => void); + }; + } +): () => void { + return () => { localStorage.clear(); const before = opts?.localStorage?.before; if (nonNullish(before)) { @@ -122,12 +162,17 @@ function testStorage( const after = opts?.localStorage?.after; if (nonNullish(after)) { const actual: LocalStorage = readLocalStorage(); - const expected: LocalStorage = after; - expect(actual).toStrictEqual(expected); + + if (typeof after === "function") { + after(actual); + } else { + const expected: LocalStorage = after; + expect(actual).toStrictEqual(expected); + } } localStorage.clear(); - }); + }; } /// Type representing the whole localStorage, used for tests.