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 @@