mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
224 lines
8.1 KiB
TypeScript
224 lines
8.1 KiB
TypeScript
import * as React from 'react';
|
|
import { createElement } from 'react';
|
|
import { createRoot, type Root } from 'react-dom/client';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
|
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
|
|
|
|
const testState = vi.hoisted(() => ({
|
|
account: {
|
|
mediaIpfsGatewayUrl: 'https://media.old.example',
|
|
plebbitOptions: {
|
|
chainProviders: {
|
|
eth: { chainId: 1, urls: ['https://eth.old.example'] },
|
|
sol: { chainId: 101, urls: ['https://sol.old.example'] },
|
|
},
|
|
httpRoutersOptions: ['https://router.old.example'],
|
|
ipfsGatewayUrls: ['https://ipfs.old.example'],
|
|
plebbitRpcClientsOptions: ['ws://old.example/key'],
|
|
pubsubHttpClientsOptions: ['https://pubsub.old.example'],
|
|
},
|
|
} as Record<string, any>,
|
|
rpcSettings: {
|
|
plebbitRpcSettings: {
|
|
plebbitOptions: {
|
|
dataPath: '/tmp/plebbit-data',
|
|
},
|
|
},
|
|
state: 'disconnected',
|
|
} as Record<string, any>,
|
|
setAccountMock: vi.fn().mockResolvedValue(undefined),
|
|
}));
|
|
|
|
vi.mock('react-i18next', () => ({
|
|
useTranslation: () => ({
|
|
t: (key: string) => key,
|
|
}),
|
|
}));
|
|
|
|
vi.mock('@bitsocialhq/bitsocial-react-hooks', () => ({
|
|
setAccount: (account: unknown) => testState.setAccountMock(account),
|
|
useAccount: () => testState.account,
|
|
usePlebbitRpcSettings: () => testState.rpcSettings,
|
|
}));
|
|
|
|
let alertSpy: ReturnType<typeof vi.spyOn>;
|
|
let container: HTMLDivElement;
|
|
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
|
const originalLocation = window.location;
|
|
let reloadMock: ReturnType<typeof vi.fn>;
|
|
let root: Root;
|
|
|
|
const loadComponent = async (isElectron = false) => {
|
|
vi.resetModules();
|
|
(window as unknown as { electronApi?: { isElectron?: boolean } }).electronApi = isElectron ? { isElectron: true } : undefined;
|
|
return (await import('../advanced-settings')).default;
|
|
};
|
|
|
|
const renderSettings = async (isElectron = false) => {
|
|
const AdvancedSettings = await loadComponent(isElectron);
|
|
await act(async () => {
|
|
root.render(createElement(AdvancedSettings));
|
|
});
|
|
};
|
|
|
|
const dispatchInput = async (element: HTMLInputElement | HTMLTextAreaElement, value: string) => {
|
|
await act(async () => {
|
|
const prototype = element instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
|
|
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
|
|
descriptor?.set?.call(element, value);
|
|
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
});
|
|
};
|
|
|
|
const clickButton = async (text: string) => {
|
|
const button = Array.from(container.querySelectorAll('button')).find((candidate) => candidate.textContent === text);
|
|
await act(async () => {
|
|
button?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
await Promise.resolve();
|
|
});
|
|
};
|
|
|
|
describe('AdvancedSettings', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
testState.account = {
|
|
mediaIpfsGatewayUrl: 'https://media.old.example',
|
|
plebbitOptions: {
|
|
chainProviders: {
|
|
eth: { chainId: 1, urls: ['https://eth.old.example'] },
|
|
sol: { chainId: 101, urls: ['https://sol.old.example'] },
|
|
},
|
|
httpRoutersOptions: ['https://router.old.example'],
|
|
ipfsGatewayUrls: ['https://ipfs.old.example'],
|
|
plebbitRpcClientsOptions: ['ws://old.example/key'],
|
|
pubsubHttpClientsOptions: ['https://pubsub.old.example'],
|
|
},
|
|
};
|
|
testState.rpcSettings = {
|
|
plebbitRpcSettings: {
|
|
plebbitOptions: {
|
|
dataPath: '/tmp/plebbit-data',
|
|
},
|
|
},
|
|
state: 'disconnected',
|
|
};
|
|
testState.setAccountMock.mockReset().mockResolvedValue(undefined);
|
|
alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => undefined);
|
|
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
reloadMock = vi.fn();
|
|
Object.defineProperty(window, 'location', {
|
|
configurable: true,
|
|
value: {
|
|
...originalLocation,
|
|
reload: reloadMock,
|
|
},
|
|
});
|
|
container = document.createElement('div');
|
|
document.body.appendChild(container);
|
|
root = createRoot(container);
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (root) {
|
|
act(() => root.unmount());
|
|
}
|
|
container?.remove();
|
|
Object.defineProperty(window, 'location', {
|
|
configurable: true,
|
|
value: originalLocation,
|
|
});
|
|
alertSpy?.mockRestore();
|
|
consoleLogSpy?.mockRestore();
|
|
});
|
|
|
|
it('saves trimmed gateway, provider, router, rpc, and chain settings', async () => {
|
|
await renderSettings(true);
|
|
|
|
const textareas = container.querySelectorAll<HTMLTextAreaElement>('textarea');
|
|
const textInputs = container.querySelectorAll<HTMLInputElement>('input[type="text"]');
|
|
expect(textareas.length).toBe(5);
|
|
expect(textInputs.length).toBe(3);
|
|
|
|
await dispatchInput(textareas[0], ' https://ipfs.one.example \n\nhttps://ipfs.two.example ');
|
|
await dispatchInput(textInputs[0], ' https://media.new.example ');
|
|
await dispatchInput(textareas[1], ' https://pubsub.one.example \n');
|
|
await dispatchInput(textareas[2], ' https://router.one.example \n');
|
|
await dispatchInput(textareas[3], ' https://eth.one.example \n');
|
|
await dispatchInput(textareas[4], ' https://sol.one.example \n');
|
|
await dispatchInput(textInputs[1], ' ws://127.0.0.1:9138/secret ');
|
|
await dispatchInput(textInputs[2], ' /tmp/next-plebbit ');
|
|
await clickButton('save_options');
|
|
|
|
expect(testState.setAccountMock).toHaveBeenCalledWith({
|
|
mediaIpfsGatewayUrl: 'https://media.new.example',
|
|
plebbitOptions: {
|
|
chainProviders: {
|
|
eth: { chainId: 1, urls: ['https://eth.one.example'] },
|
|
sol: { chainId: 101, urls: ['https://sol.one.example'] },
|
|
},
|
|
dataPath: '/tmp/next-plebbit',
|
|
httpRoutersOptions: ['https://router.one.example'],
|
|
ipfsGatewayUrls: ['https://ipfs.one.example', 'https://ipfs.two.example'],
|
|
plebbitRpcClientsOptions: ['ws://127.0.0.1:9138/secret'],
|
|
pubsubHttpClientsOptions: ['https://pubsub.one.example'],
|
|
},
|
|
});
|
|
expect(alertSpy).toHaveBeenCalledWith('Options saved, reloading...');
|
|
expect(reloadMock).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
it('disables remote-managed textareas and shows the electron data path when RPC is connected', async () => {
|
|
testState.rpcSettings = {
|
|
plebbitRpcSettings: {
|
|
plebbitOptions: {
|
|
dataPath: '/tmp/connected-node',
|
|
},
|
|
},
|
|
state: 'connected',
|
|
};
|
|
|
|
await renderSettings(true);
|
|
|
|
const textareas = container.querySelectorAll<HTMLTextAreaElement>('textarea');
|
|
const textInputs = container.querySelectorAll<HTMLInputElement>('input[type="text"]');
|
|
expect(textareas[0]?.disabled).toBe(true);
|
|
expect(textareas[1]?.disabled).toBe(true);
|
|
expect(textareas[2]?.disabled).toBe(true);
|
|
expect(textInputs[0]?.disabled).toBe(true);
|
|
expect(textInputs[2]?.disabled).toBe(false);
|
|
expect(textInputs[2]?.value).toBe('/tmp/connected-node');
|
|
});
|
|
|
|
it('toggles the node rpc instructions panel', async () => {
|
|
await renderSettings(false);
|
|
|
|
expect(container.textContent).not.toContain('secret auth key');
|
|
await clickButton('?');
|
|
expect(container.textContent).toContain('secret auth key');
|
|
await clickButton('X');
|
|
expect(container.textContent).not.toContain('secret auth key');
|
|
});
|
|
|
|
it('alerts with the error message when saving fails with an Error instance', async () => {
|
|
testState.setAccountMock.mockRejectedValueOnce(new Error('boom'));
|
|
|
|
await renderSettings(false);
|
|
await clickButton('save_options');
|
|
|
|
expect(alertSpy).toHaveBeenCalledWith('Error saving options: boom');
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(expect.objectContaining({ message: 'boom' }));
|
|
});
|
|
|
|
it('alerts with a generic message when saving fails with a non-error value', async () => {
|
|
testState.setAccountMock.mockRejectedValueOnce('bad');
|
|
|
|
await renderSettings(false);
|
|
await clickButton('save_options');
|
|
|
|
expect(alertSpy).toHaveBeenCalledWith('Error');
|
|
});
|
|
});
|