Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/serious-dragons-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/clerk-js': patch
'@clerk/clerk-expo': patch
---

Ensure the session token is updated when calling `setActive()` in a non-browser environment.
39 changes: 38 additions & 1 deletion packages/clerk-js/src/core/clerk.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ActiveSessionResource, SignInJSON, SignUpJSON, TokenResource } from '@clerk/types';
import { waitFor } from '@testing-library/dom';

import { mockNativeRuntime } from '../testUtils';
import Clerk from './clerk';
import { eventBus, events } from './events';
import type { AuthConfig, DisplayConfig, Organization } from './resources/internal';
Expand Down Expand Up @@ -91,7 +92,10 @@ describe('Clerk singleton', () => {
};

Object.defineProperty(global.window, 'location', { value: mockWindowLocation });
Object.defineProperty(global.window.document, 'hasFocus', { value: () => true, configurable: true });

if (typeof globalThis.document !== 'undefined') {
Object.defineProperty(global.window.document, 'hasFocus', { value: () => true, configurable: true });
}

const mockAddEventListener = (type: string, callback: (e: any) => void) => {
if (type === 'message') {
Expand Down Expand Up @@ -295,6 +299,39 @@ describe('Clerk singleton', () => {
expect(sut.session).toMatchObject(mockSession);
});
});

mockNativeRuntime(() => {
it('calls session.touch in a non-standard browser', async () => {
mockClientFetch.mockReturnValue(Promise.resolve({ activeSessions: [mockSession] }));

const sut = new Clerk(frontendApi);
await sut.load({ standardBrowser: false });

const executionOrder: string[] = [];
mockSession.touch.mockImplementationOnce(() => {
sut.session = mockSession as any;
executionOrder.push('session.touch');
return Promise.resolve();
});
cookieSpy.mockImplementationOnce(() => {
executionOrder.push('set cookie');
return Promise.resolve();
});
const beforeEmitMock = jest.fn().mockImplementationOnce(() => {
executionOrder.push('before emit');
return Promise.resolve();
});

await sut.setActive({ organization: { id: 'org-id' } as Organization, beforeEmit: beforeEmitMock });

expect(executionOrder).toEqual(['session.touch', 'before emit']);
expect(mockSession.touch).toHaveBeenCalled();
expect((mockSession as any as ActiveSessionResource)?.lastActiveOrganizationId).toEqual('org-id');
expect(cookieSpy).not.toHaveBeenCalled();
expect(beforeEmitMock).toBeCalledWith(mockSession);
expect(sut.session).toMatchObject(mockSession);
});
});
});

describe('.load()', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ export default class Clerk implements ClerkInterface {
//1. setLastActiveSession to passed user session (add a param).
// Note that this will also update the session's active organization
// id.
if (inActiveBrowserTab()) {
if (inActiveBrowserTab() || !this.#options.standardBrowser) {
await this.#touchLastActiveSession(newSession);
// reload session from updated client
newSession = this.#getSessionFromClient(newSession?.id);
Expand Down
37 changes: 37 additions & 0 deletions packages/clerk-js/src/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,43 @@ const render = (ui: React.ReactElement, options?: RenderOptions) => {
return { ..._render(ui, { ...options }), userEvent };
};

/**
* Helper method to mock a native runtime environment for specific test cases, currently targeted at React Native.
* Makes some assumptions about our runtime detection utilities in `packages/clerk-js/src/utils/runtime.ts`.
*
* Usage:
*
* ```js
* mockNativeRuntime(() => {
* // test cases
* it('simulates native', () => {
* expect(typeof document).toBe('undefined');
* });
* });
* ```
*/
export const mockNativeRuntime = (fn: () => void) => {
Comment thread
dimkl marked this conversation as resolved.
describe('native runtime', () => {
let spyDocument: jest.SpyInstance;
let spyNavigator: jest.SpyInstance;

beforeAll(() => {
spyDocument = jest.spyOn(globalThis, 'document', 'get');
spyDocument.mockReturnValue(undefined);

spyNavigator = jest.spyOn(globalThis.navigator, 'product', 'get');
spyNavigator.mockReturnValue('ReactNative');
});

afterAll(() => {
spyDocument.mockRestore();
spyNavigator.mockRestore();
});

fn();
});
};

export * from './ui/utils/test/runFakeTimers';
export * from './ui/utils/test/createFixtures';
export * from '@testing-library/react';
Expand Down