diff --git a/packages/ui-components/src/__tests__/InputHex.test.ts b/packages/ui-components/src/__tests__/InputHex.test.ts
new file mode 100644
index 000000000..5e038c069
--- /dev/null
+++ b/packages/ui-components/src/__tests__/InputHex.test.ts
@@ -0,0 +1,22 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/svelte';
+import InputHex from '../lib/components/input/InputHex.svelte';
+
+describe('InputHex', () => {
+ it('renders an input element', () => {
+ render(InputHex);
+ expect(screen.getByRole('textbox')).toBeTruthy();
+ });
+
+ it('initializes with empty string when no value provided', () => {
+ render(InputHex);
+ const input = screen.getByRole('textbox') as HTMLInputElement;
+ expect(input.value).toBe('');
+ });
+
+ it('displays hex value when bigint is provided', () => {
+ render(InputHex, { props: { value: 255n } });
+ const input = screen.getByRole('textbox') as HTMLInputElement;
+ expect(input.value).toBe('0xff');
+ });
+});
diff --git a/packages/ui-components/src/lib/components/input/InputHex.svelte b/packages/ui-components/src/lib/components/input/InputHex.svelte
new file mode 100644
index 000000000..2c34816c5
--- /dev/null
+++ b/packages/ui-components/src/lib/components/input/InputHex.svelte
@@ -0,0 +1,50 @@
+
+
+
diff --git a/packages/ui-components/src/lib/index.ts b/packages/ui-components/src/lib/index.ts
index abeb19ccc..d57913e5b 100644
--- a/packages/ui-components/src/lib/index.ts
+++ b/packages/ui-components/src/lib/index.ts
@@ -60,6 +60,7 @@ export { default as InputToken } from './components/input/InputToken.svelte';
export { default as CodeMirrorDotrain } from './components/CodeMirrorDotrain.svelte';
export { default as License } from './components/License.svelte';
export { default as ButtonDarkMode } from './components/ButtonDarkMode.svelte';
+export { default as InputHex } from './components/input/InputHex.svelte';
//Types
export type { AppStoresInterface } from './types/appStores.ts';
diff --git a/packages/webapp/src/app.html b/packages/webapp/src/app.html
index 77a5ff52c..655a3d271 100644
--- a/packages/webapp/src/app.html
+++ b/packages/webapp/src/app.html
@@ -3,6 +3,12 @@
+
+
+
%sveltekit.head%
diff --git a/packages/webapp/src/lib/__mocks__/MockComponent.svelte b/packages/webapp/src/lib/__mocks__/MockComponent.svelte
new file mode 100644
index 000000000..47d0e6eb2
--- /dev/null
+++ b/packages/webapp/src/lib/__mocks__/MockComponent.svelte
@@ -0,0 +1,6 @@
+
diff --git a/packages/webapp/src/lib/__mocks__/MockComponent.ts b/packages/webapp/src/lib/__mocks__/MockComponent.ts
new file mode 100644
index 000000000..eb2e74c0d
--- /dev/null
+++ b/packages/webapp/src/lib/__mocks__/MockComponent.ts
@@ -0,0 +1,3 @@
+import { writable } from 'svelte/store';
+
+export const props = writable();
diff --git a/packages/webapp/src/lib/components/Sidebar.svelte b/packages/webapp/src/lib/components/Sidebar.svelte
index 5d971ab21..77adf4c41 100644
--- a/packages/webapp/src/lib/components/Sidebar.svelte
+++ b/packages/webapp/src/lib/components/Sidebar.svelte
@@ -4,10 +4,17 @@
SidebarGroup,
SidebarItem,
SidebarWrapper,
- SidebarBrand
+ SidebarBrand,
+ CloseButton,
+ Button
} from 'flowbite-svelte';
- import { WalletSolid, ReceiptSolid, FileLinesSolid, PlusOutline } from 'flowbite-svelte-icons';
- import { page } from '$app/stores';
+ import {
+ WalletSolid,
+ ReceiptSolid,
+ FileLinesSolid,
+ PlusOutline,
+ BarsSolid
+ } from 'flowbite-svelte-icons';
import {
ButtonDarkMode,
IconTelegram,
@@ -15,70 +22,116 @@
logoDark,
logoLight
} from '@rainlanguage/ui-components';
-
+ import { onMount } from 'svelte';
export let colorTheme;
+ export let page;
+ let sideBarHidden: boolean = false;
+ let breakPoint: number = 1024;
+ let width: number;
+ $: sideBarHidden = width < breakPoint;
+ onMount(() => {
+ sideBarHidden = width < breakPoint;
+ });
+
+ const toggleSide = () => {
+ if (width < breakPoint) {
+ sideBarHidden = !sideBarHidden;
+ }
+ };
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {#if sideBarHidden}
+
+ {/if}
+
+ {#if !sideBarHidden}
+ (sideBarHidden = true)}
+ />
+ {/if}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/webapp/src/routes/+layout.svelte b/packages/webapp/src/routes/+layout.svelte
index efd62437d..377e9d86c 100644
--- a/packages/webapp/src/routes/+layout.svelte
+++ b/packages/webapp/src/routes/+layout.svelte
@@ -3,6 +3,7 @@
import { QueryClient, QueryClientProvider } from '@tanstack/svelte-query';
import Sidebar from '$lib/components/Sidebar.svelte';
import { colorTheme } from '$lib/darkMode';
+ import { page } from '$app/stores';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
@@ -14,8 +15,8 @@
-
-
+
+
diff --git a/packages/webapp/src/tests/Sidebar.test.ts b/packages/webapp/src/tests/Sidebar.test.ts
new file mode 100644
index 000000000..d48f8bed7
--- /dev/null
+++ b/packages/webapp/src/tests/Sidebar.test.ts
@@ -0,0 +1,111 @@
+import { render, cleanup, screen, fireEvent, waitFor } from '@testing-library/svelte';
+import { vi, describe, it, expect, afterEach } from 'vitest';
+import Sidebar from '../lib/components/Sidebar.svelte';
+import { writable } from 'svelte/store';
+
+vi.mock('@rainlanguage/ui-components', async () => {
+ const MockComponent = (await import('../lib/__mocks__/MockComponent.svelte')).default;
+ return {
+ ButtonDarkMode: MockComponent,
+ logoLight: 'mock-logo-light.svg',
+ logoDark: 'mock-logo-dark.svg',
+ IconTelegram: MockComponent,
+ IconExternalLink: MockComponent
+ };
+});
+
+vi.mock('svelte/store', () => {
+ return {
+ writable: () => ({
+ subscribe: () => {
+ return () => {};
+ },
+ set: vi.fn()
+ })
+ };
+});
+
+const mockWindowSize = (width: number) => {
+ Object.defineProperty(window, 'innerWidth', { writable: true, configurable: true, value: width });
+ window.dispatchEvent(new Event('resize'));
+};
+
+describe('Sidebar', () => {
+ afterEach(() => {
+ cleanup();
+ });
+
+ it('renders correctly with colorTheme store', async () => {
+ const mockColorTheme = writable('light');
+ const mockPage = {
+ url: { pathname: '/' }
+ };
+ const { container } = render(Sidebar, {
+ props: {
+ colorTheme: mockColorTheme,
+ page: mockPage
+ }
+ });
+
+ expect(container).toBeTruthy();
+ });
+ it('renders menu bars button when screen width is small', () => {
+ // Mock small screen width
+ mockWindowSize(500);
+ const mockColorTheme = writable('light');
+ const mockPage = {
+ url: { pathname: '/' }
+ };
+ render(Sidebar, { colorTheme: mockColorTheme, page: mockPage });
+
+ const barsButton = screen.getByTestId('sidebar-bars');
+ expect(barsButton).toBeInTheDocument();
+ });
+ it('shows sidebar on wide screen', () => {
+ mockWindowSize(1025);
+ const mockColorTheme = writable('light');
+ const mockPage = {
+ url: { pathname: '/' }
+ };
+ render(Sidebar, { colorTheme: mockColorTheme, page: mockPage });
+
+ expect(screen.getByTestId('sidebar')).toBeInTheDocument();
+ });
+ it('renders sidebar when bars button is clicked', async () => {
+ // Mock small screen width
+ mockWindowSize(500);
+ const mockColorTheme = writable('light');
+ const mockPage = {
+ url: { pathname: '/' }
+ };
+ render(Sidebar, { colorTheme: mockColorTheme, page: mockPage });
+
+ const barsButton = screen.getByTestId('sidebar-bars');
+ fireEvent.click(barsButton);
+
+ await waitFor(() => {
+ const sidebar = screen.getByTestId('sidebar');
+ expect(sidebar.hidden).toBe(false);
+ });
+ });
+ it('hides sidebar when close button is clicked', async () => {
+ mockWindowSize(500);
+ const mockColorTheme = writable('light');
+ const mockPage = {
+ url: { pathname: '/' }
+ };
+ render(Sidebar, { colorTheme: mockColorTheme, page: mockPage });
+
+ const barsButton = screen.getByTestId('sidebar-bars');
+ await fireEvent.click(barsButton);
+ await waitFor(() => {
+ expect(screen.getByTestId('sidebar')).toBeInTheDocument();
+ });
+ const closeButton = screen.getByTestId('close-button');
+ await fireEvent.click(closeButton);
+ await waitFor(() => {
+ const sidebar = screen.getByTestId('sidebar');
+ expect(sidebar.hidden).toBe(true);
+ });
+ });
+});
diff --git a/packages/webapp/test-setup.ts b/packages/webapp/test-setup.ts
new file mode 100644
index 000000000..bde0fc15a
--- /dev/null
+++ b/packages/webapp/test-setup.ts
@@ -0,0 +1,7 @@
+import '@testing-library/jest-dom/vitest';
+import { vi } from 'vitest';
+
+// Mock for codemirror-rainlang
+vi.mock('codemirror-rainlang', () => ({
+ RainlangLR: vi.fn()
+}));
\ No newline at end of file
diff --git a/packages/webapp/tsconfig.json b/packages/webapp/tsconfig.json
index 0b2d8865f..3c222a999 100644
--- a/packages/webapp/tsconfig.json
+++ b/packages/webapp/tsconfig.json
@@ -1,6 +1,8 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
+ "allowImportingTsExtensions": true,
+ "allowArbitraryExtensions": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
@@ -9,11 +11,13 @@
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
- "moduleResolution": "bundler"
- }
+ "module": "es2022",
+ "moduleResolution": "bundler",
+ "types": ["vitest/globals", "@testing-library/jest-dom", "vitest/importMeta", "codemirror-rainlang", "@rainlanguage/dotrain"]
+ },
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
-}
+}
\ No newline at end of file
diff --git a/packages/webapp/vite.config.ts b/packages/webapp/vite.config.ts
index d76fc8a56..95e6487e1 100644
--- a/packages/webapp/vite.config.ts
+++ b/packages/webapp/vite.config.ts
@@ -1,10 +1,25 @@
import { defineConfig } from 'vitest/config';
import { sveltekit } from '@sveltejs/kit/vite';
+import {loadEnv} from "vite";
export default defineConfig({
plugins: [sveltekit()],
test: {
- include: ['src/**/*.{test,spec}.{js,ts}']
+ // Jest like globals
+ includeSource: ['src/**/*.{js,ts}'],
+ globals: true,
+ environment: 'jsdom',
+ include: ['src/**/*.{test,spec}.ts'],
+ // Extend jest-dom matchers
+ setupFiles: ['./test-setup.ts'],
+ // load env vars
+ env: loadEnv('', process.cwd(), ''),
+ testTimeout: 10000,
+ server: {
+ deps: {
+ inline: [/@tanstack\/svelte-query/]
+ }
+ }
}
});
diff --git a/tauri-app/src/lib/components/InputHex.svelte b/tauri-app/src/lib/components/InputHex.svelte
deleted file mode 100644
index ea13114fb..000000000
--- a/tauri-app/src/lib/components/InputHex.svelte
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
diff --git a/tauri-app/src/lib/components/InputVaultId.svelte b/tauri-app/src/lib/components/InputVaultId.svelte
index 7e3deb59c..48a30a853 100644
--- a/tauri-app/src/lib/components/InputVaultId.svelte
+++ b/tauri-app/src/lib/components/InputVaultId.svelte
@@ -1,6 +1,6 @@