157 lines
5.6 KiB
TypeScript
157 lines
5.6 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import test from 'node:test'
|
|
import { computed, reactive } from 'vue'
|
|
import * as Vue from 'vue'
|
|
import { compileTemplate, parse } from '@vue/compiler-sfc'
|
|
import ts from 'typescript'
|
|
import { isMpWebviewPath } from '../src/utils/mp-webview'
|
|
|
|
const components = [
|
|
{ path: 'attachment-preview-dialog', mobileClass: 'attachment-preview-dialog--mobile' },
|
|
{ path: 'attachment-upload-field', mobileClass: 'attachment-upload-preview--mobile' }
|
|
]
|
|
|
|
for (const component of components) {
|
|
test(`${component.path}: teleported dialog mobile class follows /m routes, never desktop compact`, () => {
|
|
const file = new URL(`../src/components/business/${component.path}/index.vue`, import.meta.url)
|
|
const { descriptor } = parse(readFileSync(file, 'utf8'))
|
|
const ast = ts.createSourceFile(
|
|
'preview.ts',
|
|
descriptor.scriptSetup!.content,
|
|
ts.ScriptTarget.Latest,
|
|
true
|
|
)
|
|
const names = [
|
|
'route',
|
|
'useTiles',
|
|
'isMpPreviewToolbar',
|
|
'isCustomerPreviewToolbar',
|
|
'mpBtnSize'
|
|
]
|
|
const source = ast.statements
|
|
.filter(
|
|
(node) =>
|
|
ts.isVariableStatement(node) &&
|
|
names.includes(node.declarationList.declarations[0].name.getText(ast))
|
|
)
|
|
.map((node) => node.getText(ast))
|
|
.join('\n')
|
|
const dialog = descriptor
|
|
.template!.ast!.children.flatMap((node: any) =>
|
|
node.tag === 'el-dialog' ? [node] : node.children || []
|
|
)
|
|
.find((node: any) => node.tag === 'el-dialog') as any
|
|
assert.ok(dialog, 'real preview el-dialog must exist')
|
|
assert.ok(dialog.props.some((prop: any) => prop.name === 'append-to-body'))
|
|
const classBinding = dialog.props.find(
|
|
(prop: any) => prop.name === 'bind' && prop.arg?.content === 'class'
|
|
)
|
|
assert.ok(classBinding, 'mobile styling must bind to the teleported dialog root')
|
|
const code = ts.transpile(source, { target: ts.ScriptTarget.ESNext })
|
|
for (const compact of [false, true]) {
|
|
const route = reactive({ path: '/crm/customer' })
|
|
const bindings = { computed, isMpWebviewPath, useRoute: () => route, props: { compact } }
|
|
const state = new Function(
|
|
...Object.keys(bindings),
|
|
`${code}\nreturn { isMpPreviewToolbar, mpBtnSize, isCustomerPreviewToolbar: typeof isCustomerPreviewToolbar === 'undefined' ? undefined : isCustomerPreviewToolbar, useTiles: typeof useTiles === 'undefined' ? undefined : useTiles }`
|
|
)(...Object.values(bindings))
|
|
const readClass = () =>
|
|
new Function(
|
|
'isMpPreviewToolbar',
|
|
'useTiles',
|
|
'isCustomerPreviewToolbar',
|
|
`return (${classBinding.exp.content})`
|
|
)(
|
|
state.isMpPreviewToolbar.value,
|
|
state.useTiles?.value,
|
|
state.isCustomerPreviewToolbar?.value
|
|
)
|
|
assert.equal(Boolean(readClass()[component.mobileClass]), false)
|
|
assert.equal(
|
|
state.mpBtnSize.value,
|
|
component.path === 'attachment-upload-field' && compact ? 'small' : 'default'
|
|
)
|
|
route.path = '/m/crm/customer/form'
|
|
assert.equal(readClass()[component.mobileClass], true)
|
|
assert.equal(state.mpBtnSize.value, 'small')
|
|
route.path = '/crm/customer'
|
|
assert.equal(Boolean(readClass()[component.mobileClass]), false)
|
|
}
|
|
})
|
|
}
|
|
|
|
test('actual preview render applies touch ownership only to exact customer mobile route family', () => {
|
|
const { descriptor } = parse(
|
|
readFileSync(
|
|
new URL('../src/components/business/attachment-preview-dialog/index.vue', import.meta.url),
|
|
'utf8'
|
|
)
|
|
)
|
|
const ast = ts.createSourceFile(
|
|
'preview.ts',
|
|
descriptor.scriptSetup!.content,
|
|
ts.ScriptTarget.Latest,
|
|
true
|
|
)
|
|
const names = ['route', 'isMpPreviewToolbar', 'isCustomerPreviewToolbar', 'mpBtnSize']
|
|
const source = ast.statements
|
|
.filter(
|
|
(node) =>
|
|
ts.isVariableStatement(node) &&
|
|
names.includes(node.declarationList.declarations[0].name.getText(ast))
|
|
)
|
|
.map((node) => node.getText(ast))
|
|
.join('\n')
|
|
const route = reactive({ path: '/m/crm/customer' })
|
|
const state = new Function(
|
|
'computed',
|
|
'useRoute',
|
|
'isMpWebviewPath',
|
|
`${ts.transpile(source, { target: ts.ScriptTarget.ESNext })}; return { isMpPreviewToolbar, mpBtnSize, isCustomerPreviewToolbar: typeof isCustomerPreviewToolbar === 'undefined' ? undefined : isCustomerPreviewToolbar }`
|
|
)(computed, () => route, isMpWebviewPath)
|
|
const template = compileTemplate({
|
|
id: 'preview',
|
|
filename: 'index.vue',
|
|
source: descriptor.template!.content
|
|
})
|
|
assert.deepEqual(template.errors, [])
|
|
const exports: any = {}
|
|
new Function(
|
|
'require',
|
|
'exports',
|
|
ts.transpile(template.code, { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ESNext })
|
|
)(
|
|
() => ({
|
|
...Vue,
|
|
resolveComponent: (name: string) => name,
|
|
resolveDirective: () => undefined,
|
|
withDirectives: (node: any) => node
|
|
}),
|
|
exports
|
|
)
|
|
const ctx = Vue.proxyRefs({
|
|
...state,
|
|
visible: false,
|
|
fileName: '附件',
|
|
loading: false,
|
|
kind: 'other',
|
|
handleClosed: () => {}
|
|
})
|
|
for (const [path, owned] of [
|
|
['/m/crm/customer', true],
|
|
['/m/crm/customer/new', true],
|
|
['/m/crm/customer/123/edit', true],
|
|
['/m/crm/customer-extra', false],
|
|
['/m/crm/customerPool', false],
|
|
['/m/crm/quote/123', false],
|
|
['/m/crm/product', false],
|
|
['/crm/customer', false],
|
|
['/crm/customer/123', false]
|
|
] as const) {
|
|
route.path = path
|
|
const node = exports.render(ctx, [])
|
|
assert.equal(node.props.class.includes('attachment-preview-dialog--customer'), owned, path)
|
|
}
|
|
})
|