Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

share button fix #1298

Merged
merged 4 commits into from
Feb 14, 2025
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
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
});
});
});
}