Skip to content

Commit

Permalink
Merge pull request #1298 from rainlanguage/2024-02-14-share
Browse files Browse the repository at this point in the history
share button fix
  • Loading branch information
hardyjosh authored Feb 14, 2025
2 parents 5c26773 + c6d13db commit 13b5567
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 6 deletions.
52 changes: 52 additions & 0 deletions packages/ui-components/src/__tests__/handleShareChoices.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { handleShareChoices } from '../lib/services/handleShareChoices';
import type { DotrainOrderGui } from '@rainlanguage/orderbook/js_api';

describe('handleShareChoices', () => {
beforeEach(() => {
// Mock clipboard API
Object.assign(navigator, {
clipboard: {
writeText: vi.fn()
}
});

// Mock Svelte's page store
vi.mock('$app/stores', () => ({
page: {
subscribe: vi.fn((fn) => {
fn({ url: new URL('http://example.com') });
return () => {};
})
}
}));
});

it('should share the choices with state', async () => {
const mockGui = {
serializeState: vi.fn().mockReturnValue('mockState123')
};

await handleShareChoices(mockGui as unknown as DotrainOrderGui);

expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
'http://example.com/?state=mockState123'
);
});

it('should handle null state', async () => {
const mockGui = {
serializeState: vi.fn().mockReturnValue(null)
};

await handleShareChoices(mockGui as unknown as DotrainOrderGui);

expect(navigator.clipboard.writeText).toHaveBeenCalledWith('http://example.com/?state=');
});

it('should handle undefined gui', async () => {
await handleShareChoices(undefined as unknown as DotrainOrderGui);

expect(navigator.clipboard.writeText).toHaveBeenCalledWith('http://example.com/?state=');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import { page } from '$app/stores';
import { onMount } from 'svelte';
import ShareChoicesButton from './ShareChoicesButton.svelte';
import { handleShareChoices } from '$lib/services/handleShareChoices';
enum DeploymentStepErrors {
NO_GUI = 'Error loading GUI',
NO_STRATEGY = 'No valid strategy exists at this URL',
Expand Down Expand Up @@ -189,9 +189,9 @@
}
}
async function handleShareChoices() {
// copy the current url to the clipboard
navigator.clipboard.writeText($page.url.toString());
async function _handleShareChoices() {
if (!gui) return;
await handleShareChoices(gui);
}
onMount(async () => {
Expand Down Expand Up @@ -283,7 +283,7 @@
{:else}
<WalletConnect {appKitModal} connected={wagmiConnected} />
{/if}
<ShareChoicesButton {handleShareChoices} />
<ShareChoicesButton handleShareChoices={_handleShareChoices} />

<div class="flex flex-col">
{#if error}
Expand Down
12 changes: 12 additions & 0 deletions packages/ui-components/src/lib/services/handleShareChoices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { DotrainOrderGui } from '@rainlanguage/orderbook/js_api';
import { page } from '$app/stores';
import { get } from 'svelte/store';

export async function handleShareChoices(gui: DotrainOrderGui) {
// get the current url
const url = get(page).url;
// get the current state
const state = gui?.serializeState();
url.searchParams.set('state', state || '');
navigator.clipboard.writeText(url.toString());
}
71 changes: 70 additions & 1 deletion packages/webapp/src/lib/services/handleUpdateGuiState.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { pushState } from '$app/navigation';
import type { DotrainOrderGui } from '@rainlanguage/orderbook/js_api';
import { debounce } from 'lodash';

Expand All @@ -8,6 +9,74 @@ export function handleUpdateGuiState(gui: DotrainOrderGui) {
const pushGuiStateToUrlHistory = debounce((gui: DotrainOrderGui) => {
const serializedState = gui.serializeState();
if (serializedState) {
window.history.pushState({}, '', `?state=${serializedState}`);
pushState(`?state=${serializedState}`, { serializedState });
}
}, 1000);

if (import.meta.vitest) {
const { describe, it, expect, vi } = import.meta.vitest;

// Mock pushState
vi.mock('$app/navigation', () => ({
pushState: vi.fn()
}));

describe('handleUpdateGuiState', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

it('should push state to URL history when serializedState exists', async () => {
const mockSerializedState = 'mockSerializedState123';
const mockGui = {
serializeState: vi.fn().mockReturnValue(mockSerializedState)
} as unknown as DotrainOrderGui;

handleUpdateGuiState(mockGui);

// Fast-forward timers to trigger debounced function
await vi.advanceTimersByTimeAsync(1000);

expect(pushState).toHaveBeenCalledWith(`?state=${mockSerializedState}`, {
serializedState: mockSerializedState
});
});

it('should not push state when serializedState is falsy', async () => {
const mockGui = {
serializeState: vi.fn().mockReturnValue(null)
} as unknown as DotrainOrderGui;

handleUpdateGuiState(mockGui);

await vi.advanceTimersByTimeAsync(1000);

expect(pushState).not.toHaveBeenCalled();
});

it('should debounce multiple calls', async () => {
const mockSerializedState = 'mockSerializedState123';
const mockGui = {
serializeState: vi.fn().mockReturnValue(mockSerializedState)
} as unknown as DotrainOrderGui;

// Call multiple times in quick succession
handleUpdateGuiState(mockGui);
handleUpdateGuiState(mockGui);
handleUpdateGuiState(mockGui);

await vi.advanceTimersByTimeAsync(1000);

// Should only be called once due to debouncing
expect(pushState).toHaveBeenCalledTimes(1);
expect(pushState).toHaveBeenCalledWith(`?state=${mockSerializedState}`, {
serializedState: mockSerializedState
});
});
});
}

0 comments on commit 13b5567

Please sign in to comment.