Skip to content

Commit 120c7b9

Browse files
authored
Store and retrieve latest identity used (#3035)
* Store and retrieve latest identity used * Fix formatting * Remove export * Fix lint * Fix todo * Fix typo
1 parent e9fbf1c commit 120c7b9

File tree

7 files changed

+634
-7
lines changed

7 files changed

+634
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const storeLocalStorageKey = {
2+
LastUsedIdentities: "ii-last-used-identities",
3+
} as const;
4+
5+
export type StoreLocalStorageKey =
6+
(typeof storeLocalStorageKey)[keyof typeof storeLocalStorageKey];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2+
import { get } from "svelte/store";
3+
import {
4+
lastUsedIdentitiesStore,
5+
lastUsedIdentityStore,
6+
} from "./last-used-identities.store";
7+
import type {
8+
LastUsedIdentity,
9+
LastUsedIdentitiesData,
10+
} from "./last-used-identities.store";
11+
12+
// Mock the dependency: writableStored
13+
vi.mock("$app/environment", () => ({
14+
browser: true, // Or false, depending on the test case
15+
}));
16+
17+
describe("lastUsedIdentitiesStore", () => {
18+
const mockTimestamp1 = 1700000000000;
19+
const mockTimestamp2 = 1700000001000;
20+
const mockTimestamp3 = 1700000002000;
21+
22+
const identity1 = BigInt("111");
23+
const name1 = "Test ID 1";
24+
const identity2 = BigInt("222");
25+
const name2 = "Test ID 2";
26+
27+
beforeEach(() => {
28+
// Reset the store state and time before each test
29+
vi.useFakeTimers();
30+
vi.setSystemTime(mockTimestamp1);
31+
localStorage.clear();
32+
lastUsedIdentitiesStore.reset(); // Use the store's reset method
33+
});
34+
35+
afterEach(() => {
36+
vi.useRealTimers();
37+
});
38+
39+
it("should initialize with an empty object", () => {
40+
expect(get(lastUsedIdentitiesStore)).toEqual({});
41+
});
42+
43+
it("should add the first identity correctly", () => {
44+
lastUsedIdentitiesStore.addLatestUsed(identity1, name1);
45+
46+
const expected: LastUsedIdentitiesData = {
47+
[identity1.toString()]: {
48+
identityNumber: identity1,
49+
name: name1,
50+
lastUsedTimestampMillis: mockTimestamp1,
51+
},
52+
};
53+
expect(get(lastUsedIdentitiesStore)).toEqual(expected);
54+
});
55+
56+
it("should add multiple identities with correct timestamps", () => {
57+
// Add first identity
58+
lastUsedIdentitiesStore.addLatestUsed(identity1, name1);
59+
60+
// Advance time and add second identity
61+
vi.setSystemTime(mockTimestamp2);
62+
lastUsedIdentitiesStore.addLatestUsed(identity2, name2);
63+
64+
const expected: LastUsedIdentitiesData = {
65+
[identity1.toString()]: {
66+
identityNumber: identity1,
67+
name: name1,
68+
lastUsedTimestampMillis: mockTimestamp1,
69+
},
70+
[identity2.toString()]: {
71+
identityNumber: identity2,
72+
name: name2,
73+
lastUsedTimestampMillis: mockTimestamp2,
74+
},
75+
};
76+
expect(get(lastUsedIdentitiesStore)).toEqual(expected);
77+
});
78+
79+
it("should update the timestamp when adding an existing identity", () => {
80+
// Add identity initially
81+
lastUsedIdentitiesStore.addLatestUsed(identity1, name1);
82+
expect(
83+
get(lastUsedIdentitiesStore)[identity1.toString()]
84+
.lastUsedTimestampMillis,
85+
).toBe(mockTimestamp1);
86+
87+
// Advance time and add the same identity again
88+
vi.setSystemTime(mockTimestamp3);
89+
lastUsedIdentitiesStore.addLatestUsed(identity1, name1); // Name doesn't matter for update logic here
90+
91+
const expected: LastUsedIdentitiesData = {
92+
[identity1.toString()]: {
93+
identityNumber: identity1,
94+
name: name1, // Name should remain the same from the *last* call
95+
lastUsedTimestampMillis: mockTimestamp3,
96+
},
97+
};
98+
expect(get(lastUsedIdentitiesStore)).toEqual(expected);
99+
expect(
100+
get(lastUsedIdentitiesStore)[identity1.toString()]
101+
.lastUsedTimestampMillis,
102+
).toBe(mockTimestamp3);
103+
});
104+
105+
it("should reset the store to an empty object", () => {
106+
lastUsedIdentitiesStore.addLatestUsed(identity1, name1);
107+
expect(get(lastUsedIdentitiesStore)).not.toEqual({}); // Ensure it's not empty
108+
109+
lastUsedIdentitiesStore.reset();
110+
expect(get(lastUsedIdentitiesStore)).toEqual({});
111+
});
112+
});
113+
114+
describe("lastUsedIdentityStore (derived)", () => {
115+
const mockTimestamp1 = 1700000000000;
116+
const mockTimestamp2 = 1700000001000;
117+
const mockTimestamp3 = 1700000002000;
118+
119+
const identity1 = BigInt("101");
120+
const name1 = "Derived ID 1";
121+
const identity2 = BigInt("202");
122+
const name2 = "Derived ID 2";
123+
const identity3 = BigInt("303");
124+
const name3 = "Derived ID 3";
125+
126+
beforeEach(() => {
127+
vi.useFakeTimers();
128+
vi.setSystemTime(mockTimestamp1);
129+
localStorage.clear();
130+
lastUsedIdentitiesStore.reset(); // Reset the source store
131+
});
132+
133+
afterEach(() => {
134+
vi.useRealTimers();
135+
});
136+
137+
it("should be undefined when the source store is empty", () => {
138+
expect(get(lastUsedIdentityStore)).toBeUndefined();
139+
});
140+
141+
it("should return the only identity when one is added", () => {
142+
lastUsedIdentitiesStore.addLatestUsed(identity1, name1);
143+
144+
const expected: LastUsedIdentity = {
145+
identityNumber: identity1,
146+
name: name1,
147+
lastUsedTimestampMillis: mockTimestamp1,
148+
};
149+
expect(get(lastUsedIdentityStore)).toEqual(expected);
150+
});
151+
152+
it("should return the latest identity when multiple are added", () => {
153+
vi.setSystemTime(mockTimestamp1);
154+
lastUsedIdentitiesStore.addLatestUsed(identity2, name2);
155+
156+
vi.setSystemTime(mockTimestamp2);
157+
lastUsedIdentitiesStore.addLatestUsed(identity1, name1);
158+
159+
const expectedLatest: LastUsedIdentity = {
160+
identityNumber: identity1,
161+
name: name1,
162+
lastUsedTimestampMillis: mockTimestamp2,
163+
};
164+
expect(get(lastUsedIdentityStore)).toEqual(expectedLatest);
165+
166+
// Add identity 3 (at time 3) - Should become the latest
167+
vi.setSystemTime(mockTimestamp3);
168+
lastUsedIdentitiesStore.addLatestUsed(identity3, name3);
169+
const expectedNewest: LastUsedIdentity = {
170+
identityNumber: identity3,
171+
name: name3,
172+
lastUsedTimestampMillis: mockTimestamp3,
173+
};
174+
expect(get(lastUsedIdentityStore)).toEqual(expectedNewest);
175+
});
176+
177+
it("should update when an existing identity becomes the latest again", () => {
178+
// Add 1 at time 1
179+
lastUsedIdentitiesStore.addLatestUsed(identity1, name1);
180+
181+
// Add 2 at time 2 (latest is now 2)
182+
vi.setSystemTime(mockTimestamp2);
183+
lastUsedIdentitiesStore.addLatestUsed(identity2, name2);
184+
expect(get(lastUsedIdentityStore)?.identityNumber).toBe(identity2);
185+
186+
// Add 1 again at time 3 (latest is now 1 again)
187+
vi.setSystemTime(mockTimestamp3);
188+
lastUsedIdentitiesStore.addLatestUsed(identity1, name1);
189+
190+
const expected: LastUsedIdentity = {
191+
identityNumber: identity1,
192+
name: name1,
193+
lastUsedTimestampMillis: mockTimestamp3,
194+
};
195+
expect(get(lastUsedIdentityStore)).toEqual(expected);
196+
});
197+
198+
it("should become undefined after the source store is reset", () => {
199+
lastUsedIdentitiesStore.addLatestUsed(identity1, name1);
200+
expect(get(lastUsedIdentityStore)).toBeDefined();
201+
202+
lastUsedIdentitiesStore.reset();
203+
expect(get(lastUsedIdentityStore)).toBeUndefined();
204+
});
205+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { storeLocalStorageKey } from "$lib/constants/store.constants";
2+
import { derived, Readable } from "svelte/store";
3+
import { writableStored } from "./writable.store";
4+
5+
export type LastUsedIdentity = {
6+
name?: string;
7+
lastUsedTimestampMillis: number;
8+
identityNumber: bigint;
9+
};
10+
export type LastUsedIdentitiesData = {
11+
[identityNumber: string]: LastUsedIdentity;
12+
};
13+
type LastUsedIdentitiesStore = Readable<LastUsedIdentitiesData> & {
14+
addLatestUsed: (identityNumber: bigint, name?: string) => void;
15+
reset: () => void;
16+
};
17+
18+
export const initLastUsedIdentitiesStore = (): LastUsedIdentitiesStore => {
19+
const { subscribe, set, update } = writableStored<LastUsedIdentitiesData>({
20+
key: storeLocalStorageKey.LastUsedIdentities,
21+
defaultValue: {},
22+
version: 1,
23+
});
24+
25+
return {
26+
subscribe,
27+
addLatestUsed: (identityNumber: bigint, name?: string) => {
28+
update((lastUsedIdentities) => {
29+
lastUsedIdentities[identityNumber.toString()] = {
30+
name,
31+
lastUsedTimestampMillis: Date.now(),
32+
identityNumber,
33+
};
34+
return lastUsedIdentities;
35+
});
36+
},
37+
reset: () => {
38+
set({});
39+
},
40+
};
41+
};
42+
43+
export const lastUsedIdentitiesStore = initLastUsedIdentitiesStore();
44+
45+
export const lastUsedIdentityStore: Readable<LastUsedIdentity> = derived(
46+
lastUsedIdentitiesStore,
47+
(lastUsedIdentities) => {
48+
return Object.values(lastUsedIdentities).sort(
49+
(a, b) => b.lastUsedTimestampMillis - a.lastUsedTimestampMillis,
50+
)[0];
51+
},
52+
);

0 commit comments

Comments
 (0)