diff --git a/frontend/src/collapsible.test.js b/frontend/src/collapsible.test.js new file mode 100644 index 0000000..c3dac0c --- /dev/null +++ b/frontend/src/collapsible.test.js @@ -0,0 +1,64 @@ +import { mount } from '@vue/test-utils' +import { beforeEach, describe, expect, it } from 'vitest' +import CollapsiblePanel from './components/CollapsiblePanel.vue' + +describe('CollapsiblePanel', () => { + beforeEach(() => { + localStorage.clear() + }) + + it('renders the title and slot when open by default', () => { + const wrapper = mount(CollapsiblePanel, { + props: { title: 'Strategy' }, + slots: { default: '

content

' }, + }) + expect(wrapper.text()).toContain('Strategy') + expect(wrapper.text()).toContain('content') + expect(wrapper.find('button').attributes('aria-expanded')).toBe('true') + }) + + it('hides the slot after clicking the header', async () => { + const wrapper = mount(CollapsiblePanel, { + props: { title: 'Strategy' }, + slots: { default: '

content

' }, + }) + await wrapper.find('button').trigger('click') + expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') + // v-show keeps the node but hides it. + expect(wrapper.find('div[style*="display: none"]').exists()).toBe(true) + }) + + it('shows the summary only when collapsed', async () => { + const wrapper = mount(CollapsiblePanel, { + props: { title: 'Backtest', summary: 'BTC/USDT 5m' }, + slots: { default: '

content

' }, + }) + expect(wrapper.text()).not.toContain('BTC/USDT 5m') + await wrapper.find('button').trigger('click') + expect(wrapper.text()).toContain('BTC/USDT 5m') + }) + + it('persists the collapsed state in localStorage', async () => { + const wrapper = mount(CollapsiblePanel, { + props: { title: 'Backtest', storageKey: 'backtest' }, + slots: { default: '

content

' }, + }) + await wrapper.find('button').trigger('click') + expect(localStorage.getItem('panel:backtest')).toBe('0') + + // A fresh mount with the same key starts collapsed. + const remounted = mount(CollapsiblePanel, { + props: { title: 'Backtest', storageKey: 'backtest' }, + slots: { default: '

content

' }, + }) + expect(remounted.find('button').attributes('aria-expanded')).toBe('false') + }) + + it('honours defaultOpen when nothing is stored', () => { + const wrapper = mount(CollapsiblePanel, { + props: { title: 'Strategy', defaultOpen: false }, + slots: { default: '

content

' }, + }) + expect(wrapper.find('button').attributes('aria-expanded')).toBe('false') + }) +}) diff --git a/frontend/src/components/BacktestPanel.vue b/frontend/src/components/BacktestPanel.vue index 4e64e96..6a2f365 100644 --- a/frontend/src/components/BacktestPanel.vue +++ b/frontend/src/components/BacktestPanel.vue @@ -1,7 +1,8 @@ - + + diff --git a/frontend/src/components/CollapsiblePanel.vue b/frontend/src/components/CollapsiblePanel.vue new file mode 100644 index 0000000..2c107e2 --- /dev/null +++ b/frontend/src/components/CollapsiblePanel.vue @@ -0,0 +1,67 @@ + + + diff --git a/frontend/src/components/StrategyPanel.vue b/frontend/src/components/StrategyPanel.vue index 137a85b..2b1c885 100644 --- a/frontend/src/components/StrategyPanel.vue +++ b/frontend/src/components/StrategyPanel.vue @@ -1,4 +1,6 @@ diff --git a/frontend/src/components/SweepPanel.vue b/frontend/src/components/SweepPanel.vue index bae0453..51b5149 100644 --- a/frontend/src/components/SweepPanel.vue +++ b/frontend/src/components/SweepPanel.vue @@ -2,6 +2,7 @@ import { computed, ref, watch } from 'vue' import * as api from '../api' import { fmtMoney, pnlClass, signed } from '../format' +import CollapsiblePanel from './CollapsiblePanel.vue' const props = defineProps({ strategies: { type: Array, required: true }, @@ -21,6 +22,13 @@ const gridText = ref({}) const selected = computed(() => props.strategies.find((s) => s.key === selectedKey.value)) +const summary = computed(() => { + if (!result.value) return '' + const top = result.value.rows[0] + const best = top ? ` · best ${signed(top.metrics.total_return_pct)}%` : '' + return `${result.value.combinations} combinations${best}` +}) + watch( () => props.strategies, (list) => { @@ -84,14 +92,8 @@ async function run() {