Files
upstream-frappe-crm/frontend/tests/unit/fieldLayoutTabState.test.js
upstream-mirror db69de1c11
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1m13s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 6s
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped
snapshot of https://github.com/frappe/crm.git (develop, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:36:01 +05:00

406 lines
11 KiB
JavaScript

import { describe, it, expect, vi } from 'vitest'
import { createApp, defineComponent, h, nextTick, ref } from 'vue'
vi.mock('frappe-ui', async () => {
const { defineComponent, h } = await import('vue')
return {
Tabs: defineComponent({
props: {
modelValue: { type: Number, default: 0 },
tabs: { type: Array, default: () => [] },
},
emits: ['update:modelValue'],
setup(props, { emit, slots }) {
return () =>
h('div', [
h(
'div',
{ role: 'tablist' },
props.tabs.map((tab, index) =>
h(
'button',
{
role: 'tab',
'aria-selected': props.modelValue === index,
onClick: () => emit('update:modelValue', index),
},
tab.label,
),
),
),
h(
'div',
{ role: 'tabpanel' },
slots['tab-panel']?.({ tab: props.tabs[props.modelValue] }),
),
])
},
}),
}
})
vi.mock('@/data/document', () => ({
useDocument: () => ({
document: {
fieldPropertyOverrides: {},
},
}),
}))
vi.mock('@/components/FieldLayout/Section.vue', () => ({
default: defineComponent({
props: {
section: { type: Object, required: true },
},
setup(props) {
return () => h('section', props.section.label)
},
}),
}))
describe('FieldLayout tab state', () => {
it('keeps the selected data field tab when the layout remounts', async () => {
const { default: FieldLayout } = await import(
'@/components/FieldLayout/FieldLayout.vue'
)
const root = document.createElement('div')
document.body.appendChild(root)
const Parent = defineComponent({
setup() {
const showLayout = ref(true)
const fieldLayoutTabIndex = ref(0)
const tabs = [
{
name: 'details_tab',
label: 'Details',
sections: [{ name: 'details_section', label: 'Details' }],
},
{
name: 'data_tab',
label: 'Data',
sections: [{ name: 'data_section', label: 'Data' }],
},
]
return () =>
h('div', [
h(
'button',
{
'data-testid': 'remount',
onClick: async () => {
showLayout.value = false
await nextTick()
showLayout.value = true
},
},
'Remount',
),
showLayout.value &&
h(FieldLayout, {
tabs,
data: {},
doctype: 'CRM Deal',
isGridRow: true,
tabIndex: fieldLayoutTabIndex.value,
'onUpdate:tabIndex': (value) => {
fieldLayoutTabIndex.value = value
},
}),
])
},
})
const app = createApp(Parent)
app.mount(root)
root.querySelectorAll('[role="tab"]')[1].click()
await nextTick()
expect(root.querySelector('[aria-selected="true"]').textContent).toBe(
'Data',
)
root.querySelector('[data-testid="remount"]').click()
await nextTick()
await nextTick()
expect(root.querySelector('[aria-selected="true"]').textContent).toBe(
'Data',
)
app.unmount()
root.remove()
})
it('keeps the selected data field tab when DataFields remounts', async () => {
const { default: FieldLayout } = await import(
'@/components/FieldLayout/FieldLayout.vue'
)
const root = document.createElement('div')
document.body.appendChild(root)
const DataFieldsStub = defineComponent({
props: {
fieldLayoutTabIndex: { type: Number, default: 0 },
},
emits: ['update:fieldLayoutTabIndex'],
setup(props, { emit }) {
const tabs = [
{
name: 'details_tab',
label: 'Details',
sections: [{ name: 'details_section', label: 'Details' }],
},
{
name: 'data_tab',
label: 'Data',
sections: [{ name: 'data_section', label: 'Data' }],
},
]
return () =>
h(FieldLayout, {
tabs,
data: {},
doctype: 'CRM Deal',
isGridRow: true,
tabIndex: props.fieldLayoutTabIndex,
'onUpdate:tabIndex': (value) => {
emit('update:fieldLayoutTabIndex', value)
},
})
},
})
const Parent = defineComponent({
setup() {
const showDataFields = ref(true)
const fieldLayoutTabIndex = ref(0)
return () =>
h('div', [
h(
'button',
{
'data-testid': 'remount-data-fields',
onClick: async () => {
showDataFields.value = false
await nextTick()
showDataFields.value = true
},
},
'Remount DataFields',
),
showDataFields.value &&
h(DataFieldsStub, {
fieldLayoutTabIndex: fieldLayoutTabIndex.value,
'onUpdate:fieldLayoutTabIndex': (value) => {
fieldLayoutTabIndex.value = value
},
}),
])
},
})
const app = createApp(Parent)
app.mount(root)
root.querySelectorAll('[role="tab"]')[1].click()
await nextTick()
expect(root.querySelector('[aria-selected="true"]').textContent).toBe(
'Data',
)
root.querySelector('[data-testid="remount-data-fields"]').click()
await nextTick()
await nextTick()
expect(root.querySelector('[aria-selected="true"]').textContent).toBe(
'Data',
)
app.unmount()
root.remove()
})
it('keeps the selected tab by name when labels are duplicated and tabs reload', async () => {
const { default: FieldLayout } = await import(
'@/components/FieldLayout/FieldLayout.vue'
)
const root = document.createElement('div')
document.body.appendChild(root)
const Parent = defineComponent({
setup() {
const fieldLayoutTabIndex = ref(0)
const fieldLayoutTabName = ref('')
const tabs = ref([
{
name: 'tab_a',
label: 'New Tab',
sections: [{ name: 'section_a', label: 'First' }],
},
{
name: 'tab_b',
label: 'New Tab',
sections: [{ name: 'section_b', label: 'Second' }],
},
])
return () =>
h('div', [
h(
'button',
{
'data-testid': 'reload-tabs',
onClick: () => {
tabs.value = [
{
name: 'tab_a',
label: 'New Tab',
sections: [{ name: 'section_a_new', label: 'First' }],
},
{
name: 'tab_b',
label: 'New Tab',
sections: [{ name: 'section_b_new', label: 'Second' }],
},
]
},
},
'Reload Tabs',
),
h(FieldLayout, {
tabs: tabs.value,
data: {},
doctype: 'CRM Deal',
isGridRow: true,
tabIndex: fieldLayoutTabIndex.value,
tabName: fieldLayoutTabName.value,
'onUpdate:tabIndex': (value) => {
fieldLayoutTabIndex.value = value
},
'onUpdate:tabName': (value) => {
fieldLayoutTabName.value = value
},
}),
])
},
})
const app = createApp(Parent)
app.mount(root)
root.querySelectorAll('[role="tab"]')[1].click()
await nextTick()
expect(root.querySelector('[role="tabpanel"]').textContent).toBe('Second')
root.querySelector('[data-testid="reload-tabs"]').click()
await nextTick()
expect(root.querySelector('[role="tabpanel"]').textContent).toBe('Second')
app.unmount()
root.remove()
})
it('restores the selected tab name from session storage after page remount', async () => {
const { default: FieldLayout } = await import(
'@/components/FieldLayout/FieldLayout.vue'
)
const root = document.createElement('div')
document.body.appendChild(root)
sessionStorage.clear()
const DataFieldsStub = defineComponent({
setup() {
const doctype = 'CRM Deal'
const docname = 'DEAL-001'
const fieldLayoutTabIndex = ref(0)
const fieldLayoutTabName = ref('')
const storageKey = `fieldLayoutTab:${doctype}:${docname}`
const tabs = [
{
name: 'tab_a',
label: 'New Tab',
sections: [{ name: 'section_a', label: 'First' }],
},
{
name: 'tab_b',
label: 'New Tab',
sections: [{ name: 'section_b', label: 'Second' }],
},
]
const storedFieldLayoutTabName = sessionStorage.getItem(storageKey)
if (storedFieldLayoutTabName) {
fieldLayoutTabName.value = storedFieldLayoutTabName
}
return () =>
h(FieldLayout, {
tabs,
data: {},
doctype,
isGridRow: true,
tabIndex: fieldLayoutTabIndex.value,
tabName: fieldLayoutTabName.value,
'onUpdate:tabIndex': (value) => {
fieldLayoutTabIndex.value = value
},
'onUpdate:tabName': (value) => {
fieldLayoutTabName.value = value
sessionStorage.setItem(storageKey, value)
},
})
},
})
const Parent = defineComponent({
setup() {
const showPage = ref(true)
return () =>
h('div', [
h(
'button',
{
'data-testid': 'remount-page',
onClick: async () => {
showPage.value = false
await nextTick()
showPage.value = true
},
},
'Remount Page',
),
showPage.value && h(DataFieldsStub),
])
},
})
const app = createApp(Parent)
app.mount(root)
root.querySelectorAll('[role="tab"]')[1].click()
await nextTick()
expect(root.querySelector('[role="tabpanel"]').textContent).toBe('Second')
root.querySelector('[data-testid="remount-page"]').click()
await nextTick()
await nextTick()
expect(root.querySelector('[role="tabpanel"]').textContent).toBe('Second')
app.unmount()
root.remove()
sessionStorage.clear()
})
})