/packages/omip/taro-transformer-wx/__tests__/state.spec.ts

https://github.com/Tencent/omi · TypeScript · 824 lines · 735 code · 88 blank · 1 comment · 17 complexity · 783744e3f9fdbfa76d0c86ed52347ee7 MD5 · raw file

  1. import transform from '../src'
  2. import { buildComponent, baseCode, baseOptions, evalClass } from './utils'
  3. function removeShadowData (obj: Object) {
  4. if (obj['__data']) {
  5. delete obj['__data']
  6. }
  7. return obj
  8. }
  9. describe('State', () => {
  10. describe('使用 object pattern 从 this 取 state', () => {
  11. test('只有一个 pattern', () => {
  12. const { ast, code } = transform({
  13. ...baseOptions,
  14. code: buildComponent(
  15. `
  16. const { state } = this
  17. return (
  18. <View className={'icon-' + this.props.type}>测试 + {this.props.type}</View>
  19. )
  20. `,
  21. `state = { type: 'test' }`
  22. )
  23. })
  24. const instance = evalClass(ast)
  25. expect(instance.__state.type).toBe('test')
  26. expect(instance.state.type).toBe('test')
  27. expect(code).not.toMatch('const { state } = this')
  28. expect(code).toMatch(`const state = this.__state`)
  29. })
  30. test('state 或 props 只能单独从 this 中解构', () => {
  31. expect(() =>
  32. transform({
  33. ...baseOptions,
  34. code: buildComponent(
  35. `
  36. const { state, fuck } = this
  37. return (
  38. <View className={'icon-' + this.props.type}>测试 + {this.type}</View>
  39. )
  40. `,
  41. `state = { type: 'test' }`
  42. )
  43. })
  44. ).toThrowError(/state props 只能单独从 this 中解构/)
  45. })
  46. test('可以使用 state 关键字作为 state', () => {
  47. const { ast, code, template } = transform({
  48. ...baseOptions,
  49. code: buildComponent(
  50. `
  51. const { state } = this.state.task
  52. return (
  53. <View>{state}</View>
  54. )
  55. `,
  56. `state = { task: { state: null } }`
  57. )
  58. })
  59. const instance = evalClass(ast)
  60. expect(instance.state.state).toBe(null)
  61. expect(instance.$usedState.includes('state')).toBe(true)
  62. })
  63. test('可以使用 props 关键字作为 state', () => {
  64. const { ast, code, template } = transform({
  65. ...baseOptions,
  66. code: buildComponent(
  67. `
  68. const { props } = this.state.task
  69. return (
  70. <View>{state}</View>
  71. )
  72. `,
  73. `state = { task: { props: null } }`
  74. )
  75. })
  76. const instance = evalClass(ast)
  77. expect(instance.state.props).toBe(null)
  78. expect(instance.$usedState.includes('props')).toBe(true)
  79. })
  80. test('可以使用 style', () => {
  81. const { template } = transform({
  82. ...baseOptions,
  83. code: buildComponent(
  84. `
  85. return (
  86. <View style={'width:' + this.state.rate + 'px;'}>
  87. <View />
  88. </View>
  89. )`,
  90. `state = { rate: 5 }`
  91. )
  92. })
  93. expect(template).toMatch(`<view style="{{'width:' + rate + 'px;'}}">`)
  94. })
  95. test.skip('可以使用 template style', () => {
  96. const { template, ast } = transform({
  97. ...baseOptions,
  98. code: buildComponent(
  99. `
  100. const rate = 5;
  101. return (
  102. <View style={\`width: \$\{rate\}px;\`}>
  103. <View />
  104. </View>
  105. )`
  106. )
  107. })
  108. const instance = evalClass(ast)
  109. expect(instance.state.anonymousState__temp).toBe(undefined)
  110. expect(template).toMatch(`<view style=\"{{'width: ' + rate + 'px;'}}\">`)
  111. })
  112. test('可以使用array of object', () => {
  113. const { template, ast } = transform({
  114. ...baseOptions,
  115. code: buildComponent(
  116. `
  117. const rate = 5;
  118. return (
  119. <View test={[{ a: 1 }]}>
  120. <View />
  121. </View>
  122. )`
  123. )
  124. })
  125. const instance = evalClass(ast)
  126. expect(instance.state.anonymousState__temp).toEqual([{ a: 1 }])
  127. expect(template).toMatch(`<view test="{{anonymousState__temp}}">`)
  128. })
  129. // state 和 props 需要单独解构
  130. test.skip('多个 pattern', () => {
  131. const { ast, code } = transform({
  132. ...baseOptions,
  133. code: buildComponent(
  134. `
  135. const { state, props } = this
  136. return (
  137. <View className={'icon-' + this.props.type}>测试 + {this.props.type}</View>
  138. )
  139. `,
  140. `state = { type: 'test' }`
  141. )
  142. })
  143. const instance = evalClass(ast)
  144. expect(instance.__state.type).toBe('test')
  145. expect(instance.state.type).toBe('test')
  146. expect(code).not.toMatch('const { state } = this')
  147. expect(code).toMatch(`const { props } = this`)
  148. expect(code).toMatch(`const state = this.__state`)
  149. })
  150. })
  151. describe('可以从 this 中取值', () => {
  152. test('直接写 this.xxx', () => {
  153. const { ast, code } = transform({
  154. ...baseOptions,
  155. code: buildComponent(
  156. `
  157. return (
  158. <View>{this.list}</View>
  159. )
  160. `,
  161. `list = ['a']`
  162. )
  163. })
  164. const instance = evalClass(ast)
  165. expect(instance.state.list).toEqual(['a'])
  166. })
  167. test('从 this 解构出来出来的变量不会重复, 00269d4f55c21d5f8531ae2b6f70203f690ffa09', () => {
  168. const { ast, code } = transform({
  169. ...baseOptions,
  170. code: buildComponent(
  171. `
  172. return (
  173. <View class={this.list}>{this.list}</View>
  174. )
  175. `,
  176. `list = ['a']`
  177. )
  178. })
  179. const instance = evalClass(ast)
  180. expect(instance.state.list).toEqual(['a'])
  181. })
  182. test('从 this 解构出来出来的变量不得与 render 作用域定义的变量重复 derived from this', () => {
  183. expect(() => {
  184. transform({
  185. ...baseOptions,
  186. code: buildComponent(
  187. `
  188. const { list } = this
  189. return (
  190. <View class={list}>{this.list}</View>
  191. )
  192. `,
  193. `list = ['a']`
  194. )
  195. })
  196. }).toThrowError(/此变量声明与/)
  197. })
  198. test('从 this 解构出来出来的变量不得与 render 作用域定义的变量重复 derived from state', () => {
  199. expect(() => {
  200. transform({
  201. ...baseOptions,
  202. code: buildComponent(
  203. `
  204. const { list } = this.state
  205. return (
  206. <View class={list}>{this.list}</View>
  207. )
  208. `,
  209. `list = ['a']`
  210. )
  211. })
  212. }).toThrowError(/此变量声明与/)
  213. })
  214. test('从 this 解构出来出来的变量不得与 render 作用域定义的变量重复 derived from props ', () => {
  215. expect(() => {
  216. transform({
  217. ...baseOptions,
  218. code: buildComponent(
  219. `
  220. const { list } = this.props
  221. return (
  222. <View class={list}>{this.list}</View>
  223. )
  224. `,
  225. `list = ['a']`
  226. )
  227. })
  228. }).toThrowError(/此变量声明与/)
  229. })
  230. test('从 this 解构出来出来的变量不得与 render 作用域定义的变量重复 const decl ', () => {
  231. expect(() => {
  232. transform({
  233. ...baseOptions,
  234. code: buildComponent(
  235. `
  236. const list = []
  237. return (
  238. <View class={list}>{this.list}</View>
  239. )
  240. `,
  241. `list = ['a']`
  242. )
  243. })
  244. }).toThrowError(/此变量声明与/)
  245. })
  246. test('可以写成员表达式', () => {
  247. const { ast, code } = transform({
  248. ...baseOptions,
  249. code: buildComponent(
  250. `
  251. return (
  252. <View>{this.list.length}</View>
  253. )
  254. `,
  255. `list = ['a']`
  256. )
  257. })
  258. const instance = evalClass(ast)
  259. expect(instance.state.list).toEqual(['a'])
  260. })
  261. test('可以从 this 中解构', () => {
  262. const { ast, code } = transform({
  263. ...baseOptions,
  264. code: buildComponent(
  265. `
  266. const { list } = this
  267. return (
  268. <View>{list}</View>
  269. )
  270. `,
  271. `list = ['a']`
  272. )
  273. })
  274. const instance = evalClass(ast)
  275. expect(instance.state.list).toEqual(['a'])
  276. })
  277. test('可以从 this 中解构之后使用成员表达式', () => {
  278. const { ast, code } = transform({
  279. ...baseOptions,
  280. code: buildComponent(
  281. `
  282. const { list } = this
  283. return (
  284. <View>{list.length}</View>
  285. )
  286. `,
  287. `list = ['a']`
  288. )
  289. })
  290. const instance = evalClass(ast)
  291. expect(instance.state.list).toEqual(['a'])
  292. })
  293. test('不解构', () => {
  294. const { ast, code } = transform({
  295. ...baseOptions,
  296. code: buildComponent(
  297. `
  298. const list = this.list
  299. return (
  300. <View>{list.length}</View>
  301. )
  302. `,
  303. `list = ['a']`
  304. )
  305. })
  306. const instance = evalClass(ast)
  307. expect(instance.state.list).toEqual(['a'])
  308. })
  309. })
  310. describe('$usedState', () => {
  311. test('$usedState 一直存在并且是一个 array', () => {
  312. const { ast } = transform({
  313. ...baseOptions,
  314. code: buildComponent(baseCode)
  315. })
  316. const instance = evalClass(ast)
  317. expect(Array.isArray(instance.$usedState)).toBeTruthy()
  318. })
  319. test('没有被定义也会被加上', () => {
  320. const { ast } = transform({
  321. ...baseOptions,
  322. code: buildComponent(`return <View />`)
  323. })
  324. const instance = evalClass(ast)
  325. expect(Array.isArray(instance.$usedState)).toBeTruthy()
  326. })
  327. test('直接从 this.state 可以引用', () => {
  328. const { ast } = transform({
  329. ...baseOptions,
  330. code: buildComponent(baseCode)
  331. })
  332. const instance = evalClass(ast)
  333. expect(instance.$usedState[0]).toBe('list')
  334. })
  335. test('this.props', () => {
  336. const { ast, code, template } = transform({
  337. ...baseOptions,
  338. code: buildComponent(`
  339. return (
  340. <View test={this.props.a === this.props.b} />
  341. )
  342. `)
  343. })
  344. const instance = evalClass(ast)
  345. expect(instance.$usedState).toEqual(['a', 'b'])
  346. expect(instance.state).toEqual({})
  347. })
  348. test('props', () => {
  349. const { ast, code, template } = transform({
  350. ...baseOptions,
  351. code: buildComponent(`
  352. const { a, b } = this.props
  353. return (
  354. <View test={a === b} />
  355. )
  356. `)
  357. })
  358. const instance = evalClass(ast)
  359. expect(instance.$usedState).toEqual(['a', 'b'])
  360. })
  361. })
  362. describe('createData()', () => {
  363. test('没有定义即便使用也不会存在', () => {
  364. const { ast } = transform({
  365. ...baseOptions,
  366. code: buildComponent(`return <View />`)
  367. })
  368. const instance = evalClass(ast)
  369. expect(removeShadowData(instance._createData())).toEqual({})
  370. })
  371. test('可以从 this.state 中使用', () => {
  372. const { ast } = transform({
  373. ...baseOptions,
  374. code: buildComponent(baseCode, `state = { list: [] }`)
  375. })
  376. const instance = evalClass(ast)
  377. expect(removeShadowData(instance._createData())).toEqual({ list: [] })
  378. })
  379. test('可以从 变量定义中 中使用', () => {
  380. const { ast } = transform({
  381. ...baseOptions,
  382. code: buildComponent(
  383. `
  384. const list = this.state.list
  385. return (
  386. <View className='index'>
  387. <View className='title'>{this.state.title}</View>
  388. <View className='content'>
  389. {list.map(item => {
  390. return (
  391. <View className='item'>{item}</View>
  392. )
  393. })}
  394. <Button className='add' onClick={this.add}>添加</Button>
  395. </View>
  396. </View>
  397. )
  398. `,
  399. `state = { list: [] }`
  400. )
  401. })
  402. const instance = evalClass(ast)
  403. expect(removeShadowData(instance._createData())).toEqual({ list: [] })
  404. })
  405. test('可以从 Object pattern 定义中使用', () => {
  406. const { ast } = transform({
  407. ...baseOptions,
  408. code: buildComponent(
  409. `
  410. const { list } = this.state
  411. return (
  412. <View className='index'>
  413. <View className='title'>{this.state.title}</View>
  414. <View className='content'>
  415. {list.map(item => {
  416. return (
  417. <View className='item'>{item}</View>
  418. )
  419. })}
  420. <Button className='add' onClick={this.add}>添加</Button>
  421. </View>
  422. </View>
  423. )
  424. `,
  425. `state = { list: [] }`
  426. )
  427. })
  428. const instance = evalClass(ast)
  429. expect(removeShadowData(instance._createData())).toEqual({ list: [] })
  430. })
  431. test('map 的 callee 也需要加入 state', () => {
  432. const { ast } = transform({
  433. ...baseOptions,
  434. code: buildComponent(
  435. `
  436. const { list } = this.state
  437. const ary = list.filter(Boolean)
  438. return (
  439. <View className='index'>
  440. <View className='title'>{this.state.title}</View>
  441. <View className='content'>
  442. {ary.map(item => {
  443. return (
  444. <View className='item'>{item}</View>
  445. )
  446. })}
  447. <Button className='add' onClick={this.add}>添加</Button>
  448. </View>
  449. </View>
  450. )
  451. `,
  452. `state = { list: [] }`
  453. )
  454. })
  455. const instance = evalClass(ast)
  456. expect(removeShadowData(instance._createData())).toEqual({
  457. list: [],
  458. ary: []
  459. })
  460. })
  461. test('成员表达式的 map callee 也需要加入 state', () => {
  462. const { ast } = transform({
  463. ...baseOptions,
  464. code: buildComponent(
  465. `
  466. const { obj } = this.state
  467. const ary = obj.list
  468. return (
  469. <View className='index'>
  470. <View className='title'>{this.state.title}</View>
  471. <View className='content'>
  472. {ary.map(item => {
  473. return (
  474. <View className='item'>{item}</View>
  475. )
  476. })}
  477. <Button className='add' onClick={this.add}>添加</Button>
  478. </View>
  479. </View>
  480. )
  481. `,
  482. `state = { obj: { list: [] } }`
  483. )
  484. })
  485. const instance = evalClass(ast)
  486. expect(removeShadowData(instance._createData())).toEqual({
  487. obj: { list: [] },
  488. ary: []
  489. })
  490. })
  491. describe('自定义组件', () => {
  492. describe('Identifier', () => {
  493. test('逻辑表达式', () => {
  494. const { ast } = transform({
  495. ...baseOptions,
  496. code: buildComponent(`
  497. const a = true
  498. const b = ''
  499. return <Custom test={a && b} />
  500. `)
  501. })
  502. const instance = evalClass(ast)
  503. expect(removeShadowData(instance._createData())).toEqual({
  504. a: true,
  505. b: ''
  506. })
  507. })
  508. test('条件表达式', () => {
  509. const { ast } = transform({
  510. ...baseOptions,
  511. code: buildComponent(`
  512. const a = true
  513. const b = ''
  514. const c = ''
  515. return <Custom test={a ? b : c} />
  516. `)
  517. })
  518. const instance = evalClass(ast)
  519. expect(removeShadowData(instance._createData())).toEqual({
  520. a: true,
  521. b: '',
  522. c: ''
  523. })
  524. })
  525. test('作用域有值', () => {
  526. const { ast } = transform({
  527. ...baseOptions,
  528. code: buildComponent(`
  529. const a = true
  530. return <Custom test={a} />
  531. `)
  532. })
  533. const instance = evalClass(ast)
  534. expect(removeShadowData(instance._createData())).toEqual({ a: true })
  535. })
  536. test('作用域有值但没用到', () => {
  537. const { ast } = transform({
  538. ...baseOptions,
  539. code: buildComponent(`
  540. const a = true
  541. const b = ''
  542. const c = ''
  543. return <Custom test={a} />
  544. `)
  545. })
  546. const instance = evalClass(ast)
  547. expect(removeShadowData(instance._createData())).toEqual({ a: true })
  548. })
  549. })
  550. describe('JSXAttribute', () => {
  551. test('支持成员表达式', () => {
  552. const { ast } = transform({
  553. ...baseOptions,
  554. code: buildComponent(`
  555. const a = { a: '' }
  556. return <Custom test={a.a} />
  557. `)
  558. })
  559. const instance = evalClass(ast)
  560. expect(removeShadowData(instance._createData())).toEqual({
  561. a: { a: '' }
  562. })
  563. })
  564. test('三元表达式支持成员表达式', () => {
  565. const { ast } = transform({
  566. ...baseOptions,
  567. code: buildComponent(`
  568. const a = { a: '' }
  569. const b = { b: '' }
  570. const c = { c: '' }
  571. return <Custom test={a.a ? b.b : c.c} />
  572. `)
  573. })
  574. const instance = evalClass(ast)
  575. expect(removeShadowData(instance._createData())).toEqual({
  576. a: { a: '' },
  577. b: { b: '' },
  578. c: { c: '' }
  579. })
  580. })
  581. test('逻辑表达式支持成员表达式', () => {
  582. const { ast } = transform({
  583. ...baseOptions,
  584. code: buildComponent(`
  585. const a = { a: '' }
  586. const b = { b: '' }
  587. return <Custom test={a.a && b.b} />
  588. `)
  589. })
  590. const instance = evalClass(ast)
  591. expect(removeShadowData(instance._createData())).toEqual({
  592. a: { a: '' },
  593. b: { b: '' }
  594. })
  595. })
  596. test('逻辑表达式支持成员表达式 2', () => {
  597. const { ast } = transform({
  598. ...baseOptions,
  599. code: buildComponent(`
  600. const a = { a: '' }
  601. const b = { b: '' }
  602. return <Custom test={a.a || b.b} />
  603. `)
  604. })
  605. const instance = evalClass(ast)
  606. expect(removeShadowData(instance._createData())).toEqual({
  607. a: { a: '' },
  608. b: { b: '' }
  609. })
  610. })
  611. })
  612. })
  613. describe('自定义组件', () => {
  614. describe('Identifier', () => {
  615. test('逻辑表达式', () => {
  616. const { ast } = transform({
  617. ...baseOptions,
  618. code: buildComponent(`
  619. const a = true
  620. const b = ''
  621. return <View test={a && b} />
  622. `)
  623. })
  624. const instance = evalClass(ast)
  625. expect(removeShadowData(instance._createData())).toEqual({
  626. a: true,
  627. b: ''
  628. })
  629. })
  630. test('条件表达式', () => {
  631. const { ast } = transform({
  632. ...baseOptions,
  633. code: buildComponent(`
  634. const a = true
  635. const b = ''
  636. const c = ''
  637. return <View test={a ? b : c} />
  638. `)
  639. })
  640. const instance = evalClass(ast)
  641. expect(removeShadowData(instance._createData())).toEqual({
  642. a: true,
  643. b: '',
  644. c: ''
  645. })
  646. })
  647. test('作用域有值', () => {
  648. const { ast } = transform({
  649. ...baseOptions,
  650. code: buildComponent(`
  651. const a = true
  652. return <Custom test={a} />
  653. `)
  654. })
  655. const instance = evalClass(ast)
  656. expect(removeShadowData(instance._createData())).toEqual({ a: true })
  657. })
  658. test('作用域有值但没用到', () => {
  659. const { ast } = transform({
  660. ...baseOptions,
  661. code: buildComponent(`
  662. const a = true
  663. const b = ''
  664. const c = ''
  665. return <View test={a} />
  666. `)
  667. })
  668. const instance = evalClass(ast)
  669. expect(removeShadowData(instance._createData())).toEqual({ a: true })
  670. })
  671. })
  672. describe('JSXAttribute', () => {
  673. test('支持成员表达式', () => {
  674. const { ast } = transform({
  675. ...baseOptions,
  676. code: buildComponent(`
  677. const a = { a: '' }
  678. return <View test={a.a} />
  679. `)
  680. })
  681. const instance = evalClass(ast)
  682. expect(removeShadowData(instance._createData())).toEqual({
  683. a: { a: '' }
  684. })
  685. })
  686. test('三元表达式支持成员表达式', () => {
  687. const { ast } = transform({
  688. ...baseOptions,
  689. code: buildComponent(`
  690. const a = { a: '' }
  691. const b = { b: '' }
  692. const c = { c: '' }
  693. return <View test={a.a ? b.b : c.c} />
  694. `)
  695. })
  696. const instance = evalClass(ast)
  697. expect(removeShadowData(instance._createData())).toEqual({
  698. a: { a: '' },
  699. b: { b: '' },
  700. c: { c: '' }
  701. })
  702. })
  703. test('逻辑表达式支持成员表达式', () => {
  704. const { ast } = transform({
  705. ...baseOptions,
  706. code: buildComponent(`
  707. const a = { a: '' }
  708. const b = { b: '' }
  709. return <View test={a.a && b.b} />
  710. `)
  711. })
  712. const instance = evalClass(ast)
  713. expect(removeShadowData(instance._createData())).toEqual({
  714. a: { a: '' },
  715. b: { b: '' }
  716. })
  717. })
  718. test('逻辑表达式支持成员表达式 2', () => {
  719. const { ast } = transform({
  720. ...baseOptions,
  721. code: buildComponent(`
  722. const a = { a: '' }
  723. const b = { b: '' }
  724. return <View test={a.a || b.b} />
  725. `)
  726. })
  727. const instance = evalClass(ast)
  728. expect(removeShadowData(instance._createData())).toEqual({
  729. a: { a: '' },
  730. b: { b: '' }
  731. })
  732. })
  733. })
  734. })
  735. })
  736. })