From 2afd1883bfcc23114ad6597a6b9e3ac4edc91a6c Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 4 Nov 2025 21:10:53 -0600 Subject: [PATCH 1/3] chore(clerk-js): Add test coverage for memoizeStateListenerCallback. --- .../memoizeStateListenerCallback.test.ts | 241 +++++++++++++++--- 1 file changed, 202 insertions(+), 39 deletions(-) diff --git a/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts b/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts index a3af6124a73..8d5cea5865f 100644 --- a/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts +++ b/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts @@ -1,42 +1,205 @@ -// TODO: jest fails because of a circular dependency on Client -> Base -> Client -// This circular dep is a known issue we plan to address soon. Enable the tests then -import { describe, it } from 'vitest'; +import type { Resources, UserJSON } from '@clerk/shared/types'; +import { describe, expect, it, vi } from 'vitest'; -describe.skip('memoizeStateListenerCallback', () => { - it.skip('runs', () => { - // TODO +import { User } from '../../core/resources/User'; +import { memoizeListenerCallback } from '../memoizeStateListenerCallback'; + +describe('memoizeStateListenerCallback', () => { + it('returns same user ref if user obj state has not changed', () => { + const user1 = new User({ + id: 'u1', + updated_at: 1, + first_name: 'clerk', + email_addresses: [], + external_accounts: [], + phone_numbers: [], + web3_wallets: [], + } as unknown as UserJSON); + + const user2 = new User({ + id: 'u1', + updated_at: 1, + first_name: 'clerk', + email_addresses: [], + external_accounts: [], + phone_numbers: [], + web3_wallets: [], + } as unknown as UserJSON); + + let calledWith: Resources | undefined; + const listener = memoizeListenerCallback( + vi.fn((e: Resources) => { + calledWith = e; + }), + ); + + listener({ client: null, organization: null, session: null, user: user1 }); + listener({ client: null, organization: null, session: null, user: user2 }); + + expect(calledWith?.user).toBe(user1); }); -}); -// import { Resources, UserJSON } from '@clerk/shared/types'; -// -// const frontEndApi = ''; -// const path = ''; -// -// describe('memoizeStateListenerCallback', () => { -// it('returns same user ref if user obj state has not changed', () => { -// const user1 = new User(frontEndApi, path, { -// id: 'u1', -// updated_at: 1, -// first_name: 'clerk', -// } as UserJSON); -// -// const user2 = new User(frontEndApi, path, { -// id: 'u1', -// updated_at: 1, -// first_name: 'clerk', -// } as UserJSON); -// -// let calledWith: any; -// const listener = memoizeListenerCallback( -// jest.fn((e: Resources) => { -// console.log(e); -// calledWith = e.user; -// }), -// ); -// -// listener(({ user: user1 } as any) as Resources); -// listener(({ user: user2 } as any) as Resources); -// expect(calledWith).toBe(user1); -// }); -// }); + it('returns new user ref if user obj state has changed', () => { + const user1 = new User({ + id: 'u1', + updated_at: 1, + first_name: 'clerk', + email_addresses: [], + external_accounts: [], + phone_numbers: [], + web3_wallets: [], + } as unknown as UserJSON); + + const user2 = new User({ + id: 'u1', + updated_at: 2, + first_name: 'clerk', + email_addresses: [], + external_accounts: [], + phone_numbers: [], + web3_wallets: [], + } as unknown as UserJSON); + + let calledWith: Resources | undefined; + const listener = memoizeListenerCallback( + vi.fn((e: Resources) => { + calledWith = e; + }), + ); + + listener({ client: null, organization: null, session: null, user: user1 }); + listener({ client: null, organization: null, session: null, user: user2 }); + + expect(calledWith?.user).toBe(user2); + }); + + it('returns new user ref if user id has changed', () => { + const user1 = new User({ + id: 'u1', + updated_at: 1, + first_name: 'clerk', + email_addresses: [], + external_accounts: [], + phone_numbers: [], + web3_wallets: [], + } as unknown as UserJSON); + + const user2 = new User({ + id: 'u2', + updated_at: 1, + first_name: 'clerk', + email_addresses: [], + external_accounts: [], + phone_numbers: [], + web3_wallets: [], + } as unknown as UserJSON); + + let calledWith: Resources | undefined; + const listener = memoizeListenerCallback( + vi.fn((e: Resources) => { + calledWith = e; + }), + ); + + listener({ client: null, organization: null, session: null, user: user1 }); + listener({ client: null, organization: null, session: null, user: user2 }); + + expect(calledWith?.user).toBe(user2); + }); + + it('handles user becoming null', () => { + const user1 = new User({ + id: 'u1', + updated_at: 1, + first_name: 'clerk', + email_addresses: [], + external_accounts: [], + phone_numbers: [], + web3_wallets: [], + } as unknown as UserJSON); + + let calledWith: Resources | undefined; + const listener = memoizeListenerCallback( + vi.fn((e: Resources) => { + calledWith = e; + }), + ); + + listener({ client: null, organization: null, session: null, user: user1 }); + listener({ client: null, organization: null, session: null, user: null }); + + expect(calledWith?.user).toBe(null); + }); + + it('handles user transitioning from null to defined', () => { + const user1 = new User({ + id: 'u1', + updated_at: 1, + first_name: 'clerk', + email_addresses: [], + external_accounts: [], + phone_numbers: [], + web3_wallets: [], + } as unknown as UserJSON); + + let calledWith: Resources | undefined; + const listener = memoizeListenerCallback( + vi.fn((e: Resources) => { + calledWith = e; + }), + ); + + listener({ client: null, organization: null, session: null, user: null }); + listener({ client: null, organization: null, session: null, user: user1 }); + + expect(calledWith?.user).toBe(user1); + }); + + it('calls the callback function each time', () => { + const user1 = new User({ + id: 'u1', + updated_at: 1, + first_name: 'clerk', + email_addresses: [], + external_accounts: [], + phone_numbers: [], + web3_wallets: [], + } as unknown as UserJSON); + + const mockCallback = vi.fn(); + const listener = memoizeListenerCallback(mockCallback); + + listener({ client: null, organization: null, session: null, user: user1 }); + listener({ client: null, organization: null, session: null, user: user1 }); + + expect(mockCallback).toHaveBeenCalledTimes(2); + }); + + it('treats null and undefined as different values (null to undefined)', () => { + let calledWith: Resources | undefined; + const listener = memoizeListenerCallback( + vi.fn((e: Resources) => { + calledWith = e; + }), + ); + + listener({ client: null, organization: null, session: null, user: null }); + listener({ client: null, organization: null, session: null, user: undefined }); + + expect(calledWith?.user).toBe(undefined); + }); + + it('treats null and undefined as different values (undefined to null)', () => { + let calledWith: Resources | undefined; + const listener = memoizeListenerCallback( + vi.fn((e: Resources) => { + calledWith = e; + }), + ); + + listener({ client: null, organization: null, session: null, user: undefined }); + listener({ client: null, organization: null, session: null, user: null }); + + expect(calledWith?.user).toBe(null); + }); +}); From 789f5d39ae33e2b511daeb404b233e4b4ee17797 Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 4 Nov 2025 21:13:07 -0600 Subject: [PATCH 2/3] chore: empty changeset --- .changeset/slimy-maps-read.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changeset/slimy-maps-read.md diff --git a/.changeset/slimy-maps-read.md b/.changeset/slimy-maps-read.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/slimy-maps-read.md @@ -0,0 +1,2 @@ +--- +--- From bf619b3d6dfe254ec78e7dc7d84a4dac76a79134 Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 4 Nov 2025 21:25:20 -0600 Subject: [PATCH 3/3] wip --- .../memoizeStateListenerCallback.test.ts | 107 ++++-------------- 1 file changed, 23 insertions(+), 84 deletions(-) diff --git a/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts b/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts index 8d5cea5865f..2ad17f92cbf 100644 --- a/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts +++ b/packages/clerk-js/src/utils/__tests__/memoizeStateListenerCallback.test.ts @@ -4,27 +4,24 @@ import { describe, expect, it, vi } from 'vitest'; import { User } from '../../core/resources/User'; import { memoizeListenerCallback } from '../memoizeStateListenerCallback'; +function createTestUser(overrides: Partial = {}): User { + const defaultUserJSON: UserJSON = { + email_addresses: [], + external_accounts: [], + first_name: 'clerk', + id: 'u1', + phone_numbers: [], + updated_at: 1, + web3_wallets: [], + } as unknown as UserJSON; + + return new User({ ...defaultUserJSON, ...overrides } as unknown as UserJSON); +} + describe('memoizeStateListenerCallback', () => { it('returns same user ref if user obj state has not changed', () => { - const user1 = new User({ - id: 'u1', - updated_at: 1, - first_name: 'clerk', - email_addresses: [], - external_accounts: [], - phone_numbers: [], - web3_wallets: [], - } as unknown as UserJSON); - - const user2 = new User({ - id: 'u1', - updated_at: 1, - first_name: 'clerk', - email_addresses: [], - external_accounts: [], - phone_numbers: [], - web3_wallets: [], - } as unknown as UserJSON); + const user1 = createTestUser(); + const user2 = createTestUser(); let calledWith: Resources | undefined; const listener = memoizeListenerCallback( @@ -40,25 +37,8 @@ describe('memoizeStateListenerCallback', () => { }); it('returns new user ref if user obj state has changed', () => { - const user1 = new User({ - id: 'u1', - updated_at: 1, - first_name: 'clerk', - email_addresses: [], - external_accounts: [], - phone_numbers: [], - web3_wallets: [], - } as unknown as UserJSON); - - const user2 = new User({ - id: 'u1', - updated_at: 2, - first_name: 'clerk', - email_addresses: [], - external_accounts: [], - phone_numbers: [], - web3_wallets: [], - } as unknown as UserJSON); + const user1 = createTestUser(); + const user2 = createTestUser({ updated_at: 2 }); let calledWith: Resources | undefined; const listener = memoizeListenerCallback( @@ -74,25 +54,8 @@ describe('memoizeStateListenerCallback', () => { }); it('returns new user ref if user id has changed', () => { - const user1 = new User({ - id: 'u1', - updated_at: 1, - first_name: 'clerk', - email_addresses: [], - external_accounts: [], - phone_numbers: [], - web3_wallets: [], - } as unknown as UserJSON); - - const user2 = new User({ - id: 'u2', - updated_at: 1, - first_name: 'clerk', - email_addresses: [], - external_accounts: [], - phone_numbers: [], - web3_wallets: [], - } as unknown as UserJSON); + const user1 = createTestUser(); + const user2 = createTestUser({ id: 'u2' }); let calledWith: Resources | undefined; const listener = memoizeListenerCallback( @@ -108,15 +71,7 @@ describe('memoizeStateListenerCallback', () => { }); it('handles user becoming null', () => { - const user1 = new User({ - id: 'u1', - updated_at: 1, - first_name: 'clerk', - email_addresses: [], - external_accounts: [], - phone_numbers: [], - web3_wallets: [], - } as unknown as UserJSON); + const user1 = createTestUser(); let calledWith: Resources | undefined; const listener = memoizeListenerCallback( @@ -132,15 +87,7 @@ describe('memoizeStateListenerCallback', () => { }); it('handles user transitioning from null to defined', () => { - const user1 = new User({ - id: 'u1', - updated_at: 1, - first_name: 'clerk', - email_addresses: [], - external_accounts: [], - phone_numbers: [], - web3_wallets: [], - } as unknown as UserJSON); + const user1 = createTestUser(); let calledWith: Resources | undefined; const listener = memoizeListenerCallback( @@ -156,15 +103,7 @@ describe('memoizeStateListenerCallback', () => { }); it('calls the callback function each time', () => { - const user1 = new User({ - id: 'u1', - updated_at: 1, - first_name: 'clerk', - email_addresses: [], - external_accounts: [], - phone_numbers: [], - web3_wallets: [], - } as unknown as UserJSON); + const user1 = createTestUser(); const mockCallback = vi.fn(); const listener = memoizeListenerCallback(mockCallback);