This commit is contained in:
2026-10-16 18:25:49 +08:00
parent 2705fdbc49
commit 4806f88d7c
6 changed files with 121 additions and 167 deletions
@@ -12,12 +12,13 @@ describe('贴标附件回显', () => {
const wrapper = mountPanel(); await flushPromises()
const rows = wrapper.findAll('.el-table__body tbody tr')
expect(rows).toHaveLength(4)
expect(wrapper.text()).toContain('箱唛.pdf'); expect(wrapper.text()).toContain('SKU.pdf')
expect(wrapper.text()).toContain('面单.pdf'); expect(wrapper.text()).toContain('明细.pdf')
expect(wrapper.text()).toContain('BX-1 / SKU-A')
expect(wrapper.text()).toContain('箱唛贴标文件'); expect(wrapper.text()).toContain('SKU 贴标文件')
expect(wrapper.text()).toContain('面单文件'); expect(wrapper.text()).toContain('明细文件')
expect(wrapper.text()).not.toContain('BX-1 / SKU-A'); expect(wrapper.text()).not.toContain('文件名称'); expect(wrapper.text()).not.toContain('查看'); expect(wrapper.text()).not.toContain('箱唛.pdf')
expect(wrapper.findAll('thead th')).toHaveLength(2)
expect(getRelabelOrder).not.toHaveBeenCalled()
})
it('预览请求使用申请号、文件分类及明细编号,并可重试失败请求', async () => {
it('下载请求使用申请号、文件分类及明细编号,并可重试失败请求', async () => {
vi.mocked(downloadRelabelDocument).mockRejectedValue(new Error('文件暂不可用'))
const wrapper = mountPanel(); await flushPromises()
const rows = wrapper.findAll('.el-table__body tbody tr')
@@ -32,6 +33,6 @@ describe('贴标附件回显', () => {
const wrapper = mount(RelabelDocumentsPanel, { props: { relabelNo: 'RL-1' }, global: { plugins: [ElementPlus] } }); await flushPromises()
expect(wrapper.get('[role="alert"]').text()).toContain('资料加载失败')
await wrapper.get('[role="alert"] button').trigger('click'); await flushPromises()
expect(wrapper.text()).toContain('箱唛.pdf')
expect(wrapper.text()).toContain('箱唛贴标文件')
})
})
@@ -7,9 +7,6 @@ const loaded = ref<RelabelDetailDto | null>(null)
const loading = ref(false)
const error = ref('')
const busy = ref(false)
const previewUrl = ref('')
const previewOpen = ref(false)
const previewTitle = ref('')
let generation = 0
const current = computed(() => props.detail || loaded.value)
/** 各申请分别加载,失败只影响资料区,不阻断订单详情。 */
@@ -22,28 +19,26 @@ async function load() {
catch (cause) { if (ticket === generation) error.value = cause instanceof Error ? cause.message : '贴标资料加载失败' }
finally { if (ticket === generation) loading.value = false }
}
watch(() => [props.relabelNo, props.detail], () => { clearPreview(); void load() }, { immediate: true })
interface DocumentRow { key: string; kind: string; fileId: string; fileName: string; scope: string; itemId?: string; context?: string }
watch(() => [props.relabelNo, props.detail], () => { void load() }, { immediate: true })
interface DocumentRow { key: string; kind: string; fileId: string; fileName: string; scope: string; itemId?: string }
const documents = computed<DocumentRow[]>(() => {
const result: DocumentRow[] = []
const application = current.value?.application
if (!application) return result
// 旧总文件仅作为兼容回显,避免与已分类型的总文件重复展示。
const add = (kind: string, id: string | null | undefined, name: string | null | undefined, scope: string, itemId?: string, context?: string) => {
if (id) result.push({ key: scope + ':' + (itemId || '') + ':' + id, kind, fileId: id, fileName: name || '未命名文件', scope, itemId, context })
const add = (kind: string, id: string | null | undefined, name: string | null | undefined, scope: string, itemId?: string) => {
if (id) result.push({ key: scope + ':' + (itemId || '') + ':' + id, kind, fileId: id, fileName: name || '未命名文件', scope, itemId })
}
add('箱唛总文件', application.cartonTotalFileId, application.cartonTotalFileName, 'CARTON_TOTAL')
add('SKU 标签总文件', application.skuTotalFileId, application.skuTotalFileName, 'SKU_TOTAL')
add('箱唛贴标文件', application.cartonTotalFileId, application.cartonTotalFileName, 'CARTON_TOTAL')
add('SKU 贴标文件', application.skuTotalFileId, application.skuTotalFileName, 'SKU_TOTAL')
if (application.totalFileId !== application.cartonTotalFileId && application.totalFileId !== application.skuTotalFileId)
add('贴标总文件', application.totalFileId, application.totalFileName, 'TOTAL')
add('面单文件', application.shippingLabelFileId, application.shippingLabelFileName, 'SHIPPING')
for (const item of current.value?.items || []) add('明细文件', item.relabelFileId, item.relabelFileName, 'ITEM', String(item.id), [item.cartonMark, item.skuCode].filter(Boolean).join(' / '))
for (const item of current.value?.items || []) add('明细文件', item.relabelFileId, item.relabelFileName, 'ITEM', String(item.id))
return result
})
/** 切换申请及关闭预览时释放文件对象。 */
function clearPreview() { if (previewUrl.value) URL.revokeObjectURL(previewUrl.value); previewUrl.value = ''; previewOpen.value = false }
/** 使用带身份认证的下载接口,预览和保存共享同一归属校验。 */
async function openFile(key: string, download = false) {
/** 使用带身份认证的下载接口,保留文件归属校验和原始文件名。 */
async function downloadFile(key: string) {
const row = documents.value.find(document => document.key === key)
if (!row || busy.value) return
busy.value = true
@@ -51,32 +46,25 @@ async function openFile(key: string, download = false) {
try {
const blob = await downloadRelabelDocument(props.relabelNo, row.fileId, row.scope, row.itemId)
if (ticket !== generation) return
if (download) {
const url = URL.createObjectURL(blob); const link = document.createElement('a')
link.href = url; link.download = row.fileName; link.click(); window.setTimeout(() => URL.revokeObjectURL(url), 1000)
} else { clearPreview(); previewUrl.value = URL.createObjectURL(blob); previewTitle.value = row.fileName; previewOpen.value = true }
} catch (cause) { ElMessage.error(cause instanceof Error ? cause.message : '文件读取失败,请重试') }
finally { busy.value = false }
}
onBeforeUnmount(() => { ++generation; clearPreview() })
onBeforeUnmount(() => { ++generation })
</script>
<template>
<section class="relabel-documents" aria-label="客户上传的贴标文件">
<h3>贴标文件<span v-if="relabelNo"> · {{ relabelNo }}</span></h3>
<h3>贴标文件</h3>
<p v-if="loading">资料加载中…</p>
<p v-else-if="error" role="alert">{{ error }} <el-button link type="primary" @click="load">重试</el-button></p>
<el-table v-else :data="documents" row-key="key" border empty-text="暂无上传的贴标文件">
<el-table-column prop="kind" label="文件类型" width="150" />
<el-table-column prop="context" label="对应箱唛 / SKU" min-width="180" />
<el-table-column prop="fileName" label="文件名称" min-width="240" />
<el-table-column label="操作" width="150"><template #default="{ row }"><el-button link type="primary" :disabled="busy" @click="openFile(String(row.key))">查看</el-button><el-button link type="primary" :disabled="busy" @click="openFile(String(row.key), true)">下载</el-button></template></el-table-column>
<el-table-column prop="kind" label="文件类型" min-width="150" />
<el-table-column label="操作" width="90"><template #default="{ row }"><el-button link type="primary" :disabled="busy" @click="downloadFile(String(row.key))">下载</el-button></template></el-table-column>
</el-table>
<el-dialog v-model="previewOpen" :title="previewTitle" width="80%" append-to-body @closed="clearPreview"><iframe v-if="previewUrl" :src="previewUrl" :title="previewTitle" class="relabel-document-preview" /></el-dialog>
</section>
</template>
<style scoped>
.relabel-documents { margin: 16px 0; }
.relabel-documents h3 { margin: 0 0 12px; font-size: 14px; }
.relabel-documents h3 span { font-weight: 400; }
.relabel-document-preview { width: 100%; height: 70vh; border: 0; }
</style>
@@ -1,96 +1,87 @@
import { flushPromises, mount } from '@vue/test-utils'
import { defineComponent, h } from 'vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ElMessage } from 'element-plus'
import WarehouseTransferReviewDialog from './WarehouseTransferReviewDialog.vue'
import { getWarehouseTransfer, reviewWarehouseTransfer } from '../api/warehouseTransfers'
vi.mock('../api/warehouseTransfers', async importOriginal => ({
...await importOriginal<typeof import('../api/warehouseTransfers')>(),
const reviewedTransfer = { id: '1', transferNo: 'WT-1', packageCount: 2, needRelabel: false, status: 'WAIT_PDA_RECEIVING', items: [], cartons: [], pallets: [], relabelBindings: [] }
vi.mock('../api/warehouseTransfers', () => ({
getWarehouseTransfer: vi.fn(),
reviewWarehouseTransfer: vi.fn(),
}))
vi.mock('element-plus', () => ({ ElMessage: { success: vi.fn(), error: vi.fn() } }))
const AppDialogStub = defineComponent({
name: 'AppDialog',
props: { modelValue: Boolean },
props: { modelValue: Boolean, confirmLoading: Boolean, title: String, confirmText: String, cancelText: String },
emits: ['confirm', 'update:modelValue'],
setup(props, { slots }) {
return () => props.modelValue ? h('section', { class: 'dialog' }, slots.default?.()) : null
setup(props, { slots, emit }) {
return () => props.modelValue ? h('section', [
h('h2', props.title), slots.default?.(),
h('button', { class: 'back', disabled: props.confirmLoading, onClick: () => emit('update:modelValue', false) }, props.cancelText),
h('button', { class: 'approve', disabled: props.confirmLoading, onClick: () => emit('confirm') }, props.confirmText),
]) : null
},
})
const DataTableStub = defineComponent({
name: 'DataTable',
props: { data: { type: Array, default: () => [] } },
setup(props) {
return () => h('div', { class: 'data-table' }, JSON.stringify(props.data))
},
})
function detail(transferNo: string) {
return {
id: `id-${transferNo}`,
transferNo,
packageCount: 2,
needRelabel: false,
status: 'PENDING_REVIEW',
items: [{ id: 'item-1', skuCode: 'SKU-1', productNameZh: '测试货物', packageCount: 2 }],
cartons: [],
pallets: [],
relabelBindings: [],
}
}
function mountDialog(props: { modelValue: boolean; customerId: string; customerName: string; transferNo: string }) {
function mountDialog() {
return mount(WarehouseTransferReviewDialog, {
props,
global: {
stubs: {
AppDialog: AppDialogStub,
DataTable: DataTableStub,
LoadErrorState: true,
ApiErrorMeta: true,
ElForm: { template: '<form><slot /></form>' },
ElFormItem: { template: '<label><slot /></label>' },
ElRadioGroup: { template: '<div><slot /></div>' },
ElRadioButton: { template: '<button type="button"><slot /></button>' },
ElInput: { template: '<textarea />' },
},
},
props: { modelValue: true, customerId: '20001', customerName: '测试客户', transferNo: 'WT-1' },
global: { stubs: { AppDialog: AppDialogStub } },
})
}
describe('WarehouseTransferReviewDialog', () => {
beforeEach(() => {
vi.mocked(getWarehouseTransfer).mockReset()
vi.mocked(reviewWarehouseTransfer).mockReset()
beforeEach(() => { vi.clearAllMocks() })
it('shows a concise confirmation without fetching detail tables or review inputs', () => {
const wrapper = mountDialog()
expect(wrapper.text()).toContain('仓储转运单审核')
expect(wrapper.text()).toContain('审核通过后,转运单将进入海外仓收货打托作业。')
expect(wrapper.find('.back').text()).toBe('返回')
expect(wrapper.find('.approve').text()).toBe('审核通过')
expect(wrapper.find('textarea, input, table').exists()).toBe(false)
expect(getWarehouseTransfer).not.toHaveBeenCalled()
})
it('loads and renders review details when first mounted already open', async () => {
vi.mocked(getWarehouseTransfer).mockResolvedValue(detail('WT-1'))
it('return closes without approving or rejecting the order', async () => {
const wrapper = mountDialog()
await wrapper.find('.back').trigger('click')
expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
expect(reviewWarehouseTransfer).not.toHaveBeenCalled()
expect(wrapper.emitted('reviewed')).toBeUndefined()
})
const wrapper = mountDialog({ modelValue: true, customerId: '20001', customerName: '测试客户', transferNo: 'WT-1' })
it('approves in the selected customer scope once and refreshes only after success', async () => {
let finish!: () => void
vi.mocked(reviewWarehouseTransfer).mockReturnValue(new Promise(resolve => { finish = () => resolve(reviewedTransfer) }))
const wrapper = mountDialog()
const dialog = wrapper.findComponent(AppDialogStub)
dialog.vm.$emit('confirm')
dialog.vm.$emit('confirm')
await flushPromises()
expect(getWarehouseTransfer).toHaveBeenCalledTimes(1)
expect(getWarehouseTransfer).toHaveBeenCalledWith('20001', 'WT-1')
expect(wrapper.text()).toContain('WT-1')
expect(wrapper.text()).toContain('测试货物')
wrapper.unmount()
})
it('reloads details when an open dialog switches to another transfer', async () => {
vi.mocked(getWarehouseTransfer)
.mockResolvedValueOnce(detail('WT-1'))
.mockResolvedValueOnce(detail('WT-2'))
const wrapper = mountDialog({ modelValue: true, customerId: '20001', customerName: '测试客户', transferNo: 'WT-1' })
await flushPromises()
await wrapper.setProps({ customerId: '20002', transferNo: 'WT-2' })
expect(reviewWarehouseTransfer).toHaveBeenCalledExactlyOnceWith('20001', 'WT-1', 'APPROVE')
expect(dialog.props('confirmLoading')).toBe(true)
expect(wrapper.emitted('reviewed')).toBeUndefined()
finish()
await flushPromises()
expect(wrapper.emitted('reviewed')).toHaveLength(1)
expect(wrapper.emitted('update:modelValue')).toEqual([[false]])
expect(dialog.props('confirmLoading')).toBe(false)
})
expect(getWarehouseTransfer).toHaveBeenLastCalledWith('20002', 'WT-2')
expect(wrapper.text()).toContain('WT-2')
wrapper.unmount()
it('keeps the dialog open and allows retry after an approval failure', async () => {
vi.mocked(reviewWarehouseTransfer).mockRejectedValueOnce(new Error('审核失败')).mockResolvedValueOnce(reviewedTransfer)
const wrapper = mountDialog()
await wrapper.find('.approve').trigger('click')
await flushPromises()
expect(ElMessage.error).toHaveBeenCalled()
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
expect(wrapper.emitted('reviewed')).toBeUndefined()
expect(wrapper.findComponent(AppDialogStub).props('confirmLoading')).toBe(false)
await wrapper.find('.approve').trigger('click')
await flushPromises()
expect(wrapper.emitted('reviewed')).toHaveLength(1)
})
})
@@ -1,87 +1,44 @@
<script setup lang="ts">
import '@/styles/adminDensity.css'
import { computed, reactive, ref, watch } from 'vue'
import { CheckCircle2, XCircle } from '@lucide/vue'
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue'
import { getApiErrorDetails } from '@/shared/api/http'
import AppDialog from '@/shared/components/AppDialog.vue'
import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue'
import LoadErrorState from '@/shared/components/LoadErrorState.vue'
import { getWarehouseTransfer, reviewWarehouseTransfer, type WarehouseTransferDetailDto } from '../api/warehouseTransfers'
import { reviewWarehouseTransfer } from '../api/warehouseTransfers'
const props = defineProps<{ modelValue: boolean; customerId: string; customerName: string; transferNo: string }>()
const emit = defineEmits<{ 'update:modelValue': [value: boolean]; reviewed: [] }>()
const open = computed({ get: () => props.modelValue, set: (value) => emit('update:modelValue', value) })
const detail = ref<WarehouseTransferDetailDto | null>(null)
const loading = ref(false)
const saving = ref(false)
const error = ref<ApiErrorDetails | null>(null)
const form = reactive<{ decision: 'APPROVE' | 'REJECT'; remark: string }>({ decision: 'APPROVE', remark: '' })
const itemColumns: DataTableColumn[] = [
{ key: 'skuCode', label: 'SKU', minWidth: 140 }, { key: 'productNameZh', label: '品名', minWidth: 160 },
{ key: 'packageCount', label: '箱数', width: 80, align: 'right' }, { key: 'shippingMark', label: '客户箱唛', minWidth: 160 },
]
const bindingColumns: DataTableColumn[] = [
{ key: 'operationType', label: '换标类型', minWidth: 120 }, { key: 'customerCartonMark', label: '客户箱唛', minWidth: 150 },
{ key: 'originalSku', label: '原 SKU', minWidth: 120 }, { key: 'newSku', label: '新 SKU', minWidth: 120 },
{ key: 'quantity', label: '数量', width: 80, align: 'right' }, { key: 'fbaCartonMark', label: '新箱唛', minWidth: 150 },
]
const palletColumns: DataTableColumn[] = [
{ key: 'sequenceNo', label: '托序', width: 70, align: 'center' }, { key: 'palletNo', label: '托盘号', minWidth: 150 },
{ key: 'cartonCount', label: '箱数', width: 80, align: 'right' }, { key: 'volumeCbm', label: '托盘总体积(m³)', width: 150, align: 'right' },
{ key: 'grossWeightKg', label: '托盘总重量(kg)', width: 150, align: 'right' },
]
async function load() {
if (!props.customerId || !props.transferNo) return
loading.value = true; error.value = null; detail.value = null
try { detail.value = await getWarehouseTransfer(props.customerId, props.transferNo) }
catch (cause) { error.value = getApiErrorDetails(cause, '转运单审核资料加载失败,请重试') }
finally { loading.value = false }
}
async function submit() {
if (saving.value) return
if (form.decision === 'REJECT' && !form.remark.trim()) { ElMessage.warning('驳回时请填写审核原因'); return }
if (saving.value || !open.value || !props.customerId || !props.transferNo) return
saving.value = true
try {
await reviewWarehouseTransfer(props.customerId, props.transferNo, form.decision, form.remark.trim() || undefined)
ElMessage.success(form.decision === 'APPROVE' ? '转运单审核通过,已推送 PDA 收货打托' : '转运单已驳回')
open.value = false; emit('reviewed')
} catch (cause) { ElMessage.error(getApiErrorDetails(cause, '审核提交失败,请重试').message) }
finally { saving.value = false }
await reviewWarehouseTransfer(props.customerId, props.transferNo, 'APPROVE')
ElMessage.success('转运单审核通过,已推送 PDA 收货打托')
open.value = false
emit('reviewed')
} catch (cause) {
ElMessage.error(getApiErrorDetails(cause, '审核提交失败,请重试').message)
} finally {
saving.value = false
}
}
watch(
() => [props.modelValue, props.customerId, props.transferNo] as const,
([value]) => {
if (!value) return
form.decision = 'APPROVE'
form.remark = ''
void load()
},
{ immediate: true },
)
</script>
<template>
<AppDialog class="admin-density-dialog" v-model="open" title="审核仓储转运单" width="min(920px, calc(100vw - 28px))" confirm-text="提交审核" :confirm-loading="saving" :loading="loading" @confirm="submit">
<LoadErrorState v-if="error" compact title="审核资料加载失败" :message="error.message" @retry="load"><template #details><ApiErrorMeta :details="error" /></template></LoadErrorState>
<el-form v-else-if="detail" label-position="top" class="review-form density-form">
<div class="review-heading"><strong>{{ detail.transferNo }}</strong><span>{{ customerName }} · {{ detail.packageCount }} 箱 · {{ detail.needRelabel ? '需要换标' : '无需换标' }}</span></div>
<h3>货物明细</h3><DataTable :data="detail.items" :columns="itemColumns" :show-pagination="false" row-key="id" empty-text="暂无货物明细" />
<template v-if="detail.needRelabel"><h3>换标绑定明细</h3><DataTable :data="detail.relabelBindings" :columns="bindingColumns" :show-pagination="false" row-key="customerCartonMark" empty-text="暂无换标绑定明细" /></template>
<template v-if="detail.pallets?.length"><h3>PDA 打托实测</h3><DataTable :data="detail.pallets" :columns="palletColumns" :show-pagination="false" row-key="id" empty-text="暂无打托数据" /></template>
<el-form-item label="审核结论" required><el-radio-group v-model="form.decision"><el-radio-button value="APPROVE"><CheckCircle2 :size="15" />审核通过</el-radio-button><el-radio-button value="REJECT"><XCircle :size="15" />驳回</el-radio-button></el-radio-group></el-form-item>
<el-form-item :label="form.decision === 'REJECT' ? '驳回原因' : '审核备注'" :required="form.decision === 'REJECT'"><el-input v-model="form.remark" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="请输入审核备注(选填)" /></el-form-item>
</el-form>
<AppDialog
v-model="open"
class="admin-density-dialog density-confirm-dialog"
title="仓储转运单审核"
width="min(420px, calc(100vw - 28px))"
align-center
cancel-text="返回"
confirm-text="审核通过"
:confirm-loading="saving"
@confirm="submit"
>
<div>审核通过后,转运单将进入海外仓收货打托作业。</div>
</AppDialog>
</template>
<style scoped>
.review-form { display: grid; gap: var(--admin-space-5); }
.review-heading { display: flex; align-items: baseline; justify-content: space-between; gap: var(--admin-space-5); }
.review-heading span { color: var(--cc-muted); font-size: var(--admin-font-caption); }
h3 { margin: 2px 0 0; font-size: var(--admin-font-section); }
.review-form :deep(.el-form-item) { margin: var(--admin-space-1) 0 0; }
.review-form :deep(.el-radio-button__inner) { display: inline-flex; align-items: center; gap: var(--admin-space-2); }
</style>
+4
View File
@@ -53,6 +53,10 @@
.admin-density-dialog.el-dialog .el-dialog__title { font-size: var(--admin-font-title, 16px); line-height: 24px; font-weight: 600; }
.admin-density-dialog.el-dialog .el-dialog__body { min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: var(--admin-space-5, 12px) var(--admin-space-6, 16px); }
.admin-density-dialog.el-dialog .el-dialog__footer { flex: 0 0 auto; padding: var(--admin-space-3, 8px) var(--admin-space-6, 16px); border-top: 1px solid var(--cc-border); }
/* Compact confirmations keep one message and right-aligned actions without section dividers. */
.admin-density-dialog.density-confirm-dialog.el-dialog:not(.is-fullscreen) { margin: auto; }
.admin-density-dialog.density-confirm-dialog.el-dialog .el-dialog__header { border-bottom: 0; }
.admin-density-dialog.density-confirm-dialog.el-dialog .el-dialog__footer { border-top: 0; padding-bottom: var(--admin-space-5, 12px); }
.admin-density-dialog.el-dialog .el-table { font-size: var(--admin-font-body, 13px); }
.admin-density-dialog.el-dialog .el-table__cell { height: var(--admin-row-height, 36px); padding: var(--admin-space-1, 4px) 0; }
.admin-density-dialog.el-dialog .el-table .cell { line-height: 22px; }
+13
View File
@@ -171,6 +171,15 @@ const cssRoot = (css: string) => {
}
describe('approved Admin density ownership', () => {
it('opts transfer approval into the shared centered confirmation appearance', () => {
const dialog = read('modules/overseas-warehouse/components/WarehouseTransferReviewDialog.vue')
const css = read('styles/adminDensity.css')
expect(dialog).toContain('class="admin-density-dialog density-confirm-dialog"')
expect(dialog).toContain('align-center')
expect(css).toContain('.admin-density-dialog.density-confirm-dialog.el-dialog:not(.is-fullscreen) { margin: auto; }')
expect(css).toContain('.admin-density-dialog.density-confirm-dialog.el-dialog .el-dialog__header { border-bottom: 0; }')
expect(css).toContain('.admin-density-dialog.density-confirm-dialog.el-dialog .el-dialog__footer { border-top: 0;')
})
it('keeps appointment dialog content scrollable with fixed controls and unclipped calendar days', () => {
const declarations = (file: string, selector: string) => {
const values: Record<string, string> = {}
@@ -443,6 +452,10 @@ describe('approved Admin density ownership', () => {
for (const node of nodes) {
if (node.type !== 1) continue
const classes = classesOf(node)
if (classes.includes('density-confirm-dialog')) {
expect(classes, `${file}: confirmation density`).toContain('admin-density-dialog')
checked += 1
}
if (node.tag === 'DataTable' || node.tag === 'LoadingListDetailDrawer' || classes.some(name => ['density-action', 'density-actions', 'density-tabs', 'density-fields', 'density-table', 'density-tab-icon', 'density-back'].includes(name))) {
expect(insideDensity, `${file}: ${classes.join(' ')} lacks its density ancestor`).toBe(true)
checked += 1