mobile-attachment-preview.test.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. import assert from 'node:assert/strict'
  2. import { readFileSync } from 'node:fs'
  3. import test from 'node:test'
  4. import { computed, reactive } from 'vue'
  5. import { parse } from '@vue/compiler-sfc'
  6. import ts from 'typescript'
  7. import { isMpWebviewPath } from '../src/utils/mp-webview'
  8. const components = [
  9. { path: 'attachment-preview-dialog', mobileClass: 'attachment-preview-dialog--mobile' },
  10. { path: 'attachment-upload-field', mobileClass: 'attachment-upload-preview--mobile' }
  11. ]
  12. for (const component of components) {
  13. test(`${component.path}: teleported dialog mobile class follows /m routes, never desktop compact`, () => {
  14. const file = new URL(`../src/components/business/${component.path}/index.vue`, import.meta.url)
  15. const { descriptor } = parse(readFileSync(file, 'utf8'))
  16. const ast = ts.createSourceFile(
  17. 'preview.ts',
  18. descriptor.scriptSetup!.content,
  19. ts.ScriptTarget.Latest,
  20. true
  21. )
  22. const names = ['route', 'useTiles', 'isMpPreviewToolbar', 'mpBtnSize']
  23. const source = ast.statements
  24. .filter(
  25. (node) =>
  26. ts.isVariableStatement(node) &&
  27. names.includes(node.declarationList.declarations[0].name.getText(ast))
  28. )
  29. .map((node) => node.getText(ast))
  30. .join('\n')
  31. const dialog = descriptor
  32. .template!.ast!.children.flatMap((node: any) =>
  33. node.tag === 'el-dialog' ? [node] : node.children || []
  34. )
  35. .find((node: any) => node.tag === 'el-dialog') as any
  36. assert.ok(dialog, 'real preview el-dialog must exist')
  37. assert.ok(dialog.props.some((prop: any) => prop.name === 'append-to-body'))
  38. const classBinding = dialog.props.find(
  39. (prop: any) => prop.name === 'bind' && prop.arg?.content === 'class'
  40. )
  41. assert.ok(classBinding, 'mobile styling must bind to the teleported dialog root')
  42. const code = ts.transpile(source, { target: ts.ScriptTarget.ESNext })
  43. for (const compact of [false, true]) {
  44. const route = reactive({ path: '/crm/customer' })
  45. const bindings = { computed, isMpWebviewPath, useRoute: () => route, props: { compact } }
  46. const state = new Function(
  47. ...Object.keys(bindings),
  48. `${code}\nreturn { isMpPreviewToolbar, mpBtnSize, useTiles: typeof useTiles === 'undefined' ? undefined : useTiles }`
  49. )(...Object.values(bindings))
  50. const readClass = () =>
  51. new Function('isMpPreviewToolbar', 'useTiles', `return (${classBinding.exp.content})`)(
  52. state.isMpPreviewToolbar.value,
  53. state.useTiles?.value
  54. )
  55. assert.equal(Boolean(readClass()[component.mobileClass]), false)
  56. assert.equal(
  57. state.mpBtnSize.value,
  58. component.path === 'attachment-upload-field' && compact ? 'small' : 'default'
  59. )
  60. route.path = '/m/crm/customer/form'
  61. assert.equal(readClass()[component.mobileClass], true)
  62. assert.equal(state.mpBtnSize.value, 'small')
  63. route.path = '/crm/customer'
  64. assert.equal(Boolean(readClass()[component.mobileClass]), false)
  65. }
  66. })
  67. }