From 11d964b58a0cb6bfba9b3ca8f24b6b22158439e4 Mon Sep 17 00:00:00 2001 From: yuche Date: Tue, 27 Aug 2019 15:30:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(with-weapp):=20=E5=8A=A0=E5=85=A5=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../taro-with-weapp/__tests__/lifecycle.jsx | 358 ++++++++++++++++++ packages/taro-with-weapp/__tests__/props.jsx | 183 +++++++++ packages/taro-with-weapp/__tests__/state.jsx | 114 ++++++ packages/taro-with-weapp/__tests__/utils.js | 17 + packages/taro-with-weapp/jest.config.js | 31 ++ packages/taro-with-weapp/package.json | 11 +- packages/taro-with-weapp/src/index.ts | 48 ++- 7 files changed, 744 insertions(+), 18 deletions(-) create mode 100644 packages/taro-with-weapp/__tests__/lifecycle.jsx create mode 100644 packages/taro-with-weapp/__tests__/props.jsx create mode 100644 packages/taro-with-weapp/__tests__/state.jsx create mode 100644 packages/taro-with-weapp/__tests__/utils.js create mode 100644 packages/taro-with-weapp/jest.config.js diff --git a/packages/taro-with-weapp/__tests__/lifecycle.jsx b/packages/taro-with-weapp/__tests__/lifecycle.jsx new file mode 100644 index 000000000000..c1b7e430a332 --- /dev/null +++ b/packages/taro-with-weapp/__tests__/lifecycle.jsx @@ -0,0 +1,358 @@ +/** @jsx createElement */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/camelcase */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { createElement, render } from 'nervjs' +import withWeapp from '../src' +import { TaroComponent, delay } from './utils' +import * as sinon from 'sinon' + +describe('lifecycle', () => { + /** + * @type {Element} scratch + */ + let scratch + + beforeEach(() => { + scratch = document.createElement('div') + }) + + test('created', () => { + const spy = jest.fn() + + @withWeapp({ + created () { + spy() + } + }) + class A extends TaroComponent { + render () { + return
+ } + } + + render(, scratch) + + expect(spy).toBeCalled() + }) + + test('onLoad', () => { + const spy = jest.fn() + + @withWeapp({ + onLoad () { + spy() + } + }) + class A extends TaroComponent { + render () { + return
+ } + } + + render(, scratch) + + expect(spy).toBeCalled() + }) + + test('onLanuch', (done) => { + const spy = jest.fn() + + @withWeapp({ + data: { + a: '' + }, + onLanuch () { + spy() + this.a = 'a' + } + }) + class A extends TaroComponent { + render () { + return
{this.a}
+ } + } + + render(
, scratch) + + expect(spy).toBeCalled() + + delay(() => { + expect(scratch.textContent).toBe('a') + done() + }) + }) + + test('onReady', (done) => { + const s1 = sinon.spy() + const s2 = sinon.spy() + + @withWeapp({ + data: { + a: '' + }, + created () { + s1() + this.a = 'a' + }, + onReady () { + s2() + } + }) + class A extends TaroComponent { + render () { + return
{this.a}
+ } + } + + render(
, scratch) + + expect(s1.called).toBeTruthy() + expect(s2.called).toBeTruthy() + + expect(s1.calledBefore(s2)) + + delay(() => { + expect(scratch.textContent).toBe('a') + done() + }) + }) + + test('ready should work', (done) => { + const s1 = sinon.spy() + const s2 = sinon.spy() + + @withWeapp({ + data: { + a: '' + }, + created () { + s1() + this.a = 'a' + }, + ready () { + s2() + } + }) + class A extends TaroComponent { + render () { + return
{this.a}
+ } + } + + render(
, scratch) + + expect(s1.called).toBeTruthy() + expect(s2.called).toBeTruthy() + + expect(s1.calledBefore(s2)) + + delay(() => { + expect(scratch.textContent).toBe('a') + done() + }) + }) + + test('detached', () => { + const s1 = sinon.spy() + + @withWeapp({ + data: { + a: '' + }, + detached () { + s1() + } + }) + class A extends TaroComponent { + render () { + return
{this.a}
+ } + } + + render(
, scratch) + + render(
, scratch) + + expect(s1.callCount).toBe(1) + }) + + test('detached', () => { + const s1 = sinon.spy() + + @withWeapp({ + data: { + a: '' + }, + detached () { + s1() + } + }) + class A extends TaroComponent { + render () { + return
{this.a}
+ } + } + + render(
, scratch) + + render(
, scratch) + + expect(s1.callCount).toBe(1) + }) + + test('onUnload', () => { + const s1 = sinon.spy() + + @withWeapp({ + data: { + a: '' + }, + onUnload () { + s1() + } + }) + class A extends TaroComponent { + render () { + return
{this.a}
+ } + } + + render(
, scratch) + + render(
, scratch) + + expect(s1.callCount).toBe(1) + }) + + test('page lifecycle', () => { + const onLoad = sinon.spy() + const onReady = sinon.spy() + const onUnload = sinon.spy() + + @withWeapp({ + data: { + a: '' + }, + onLoad () { + onLoad() + }, + onReady () { + onReady() + }, + onUnload () { + onUnload() + } + }) + class A extends TaroComponent { + render () { + return
{this.a}
+ } + } + + render(
, scratch) + + render(
, scratch) + + expect(onLoad.callCount).toBe(1) + expect(onReady.callCount).toBe(1) + expect(onUnload.callCount).toBe(1) + + expect(onLoad.calledBefore(onReady)).toBeTruthy() + expect(onReady.calledBefore(onUnload)).toBeTruthy() + }) + + test('component lifecycle', () => { + const created = sinon.spy() + const ready = sinon.spy() + const detached = sinon.spy() + + @withWeapp({ + data: { + a: '' + }, + created () { + created() + }, + ready () { + ready() + }, + detached () { + detached() + } + }) + class A extends TaroComponent { + render () { + return
{this.a}
+ } + } + + render(
, scratch) + + render(
, scratch) + + expect(created.callCount).toBe(1) + expect(ready.callCount).toBe(1) + expect(detached.callCount).toBe(1) + + expect(created.calledBefore(ready)).toBeTruthy() + expect(ready.calledBefore(detached)).toBeTruthy() + }) + + test('created should emit this.$router.params as aruguments', () => { + const spy = jest.fn() + + @withWeapp({ + created (options) { + spy(options) + } + }) + class A extends TaroComponent { + render () { + return
+ } + } + + render(, scratch) + + expect(spy).toBeCalledWith({ a: 1 }) + }) + + test('onLoad should emit this.$router.params as aruguments', () => { + const spy = jest.fn() + + @withWeapp({ + onLoad (options) { + spy(options) + } + }) + class A extends TaroComponent { + render () { + return
+ } + } + + render(, scratch) + + expect(spy).toBeCalledWith({ a: 1 }) + }) + + test('onLanuch should emit this.$router.params as aruguments', () => { + const spy = jest.fn() + + @withWeapp({ + onLanuch (options) { + spy(options) + } + }) + class A extends TaroComponent { + render () { + return
+ } + } + + render(, scratch) + + expect(spy).toBeCalledWith({ a: 1 }) + }) +}) diff --git a/packages/taro-with-weapp/__tests__/props.jsx b/packages/taro-with-weapp/__tests__/props.jsx new file mode 100644 index 000000000000..e069c84bfe8b --- /dev/null +++ b/packages/taro-with-weapp/__tests__/props.jsx @@ -0,0 +1,183 @@ +/** @jsx createElement */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/camelcase */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { createElement, render } from 'nervjs' +import withWeapp from '../src' +import { TaroComponent, delay } from './utils' + +describe('lifecycle', () => { + /** + * @type {Element} scratch + */ + let scratch + + beforeEach(() => { + scratch = document.createElement('div') + }) + + test('default props should work', (done) => { + @withWeapp({ + properties: { + a: { + type: String, + value: 'a' + } + } + }) + class A extends TaroComponent { + render () { + return
{this.data.a}
+ } + } + + render(
, scratch) + + delay(() => { + expect(scratch.textContent).toBe('a') + done() + }) + }) + + test('can access from this.data', (done) => { + @withWeapp({ + properties: { + a: { + type: String, + value: 'a' + } + } + }) + class A extends TaroComponent { + render () { + return
{this.data.a}
+ } + } + + @withWeapp({}) + class B extends TaroComponent { + render () { + return
+ } + } + + render(, scratch) + + delay(() => { + expect(scratch.textContent).toBe('b') + done() + }) + }) + + test('observer should emit in first render', () => { + const spy = jest.fn() + + @withWeapp({ + properties: { + a: { + type: String, + value: 'a', + observer: (newVal, oldVal) => { + spy(newVal, oldVal) + } + } + } + }) + class A extends TaroComponent { + render () { + return
{this.data.a}
+ } + } + + @withWeapp({}) + class B extends TaroComponent { + render () { + return
+ } + } + + render(, scratch) + + expect(scratch.textContent).toBe('b') + expect(spy).toBeCalled() + expect(spy).toBeCalledWith('b', 'b') + }) + + test('observer should work', () => { + const spy = jest.fn() + + @withWeapp({ + properties: { + a: { + type: String, + value: 'a', + observer: (newVal, oldVal) => { + spy(newVal, oldVal) + } + } + } + }) + class A extends TaroComponent { + render () { + return
{this.data.a}
+ } + } + + let inst + + @withWeapp({ + data: { + a: 'a' + } + }) + class B extends TaroComponent { + constructor (props) { + super(props) + inst = this + } + render () { + return
+ } + } + + render(, scratch) + + expect(scratch.textContent).toBe('a') + expect(spy).toBeCalled() + expect(spy).toBeCalledWith('a', 'a') + + inst.setData({ a: 'b' }) + inst.forceUpdate() + expect(spy).toBeCalledWith('b', 'a') + }) + + test('trigger event should work', () => { + const spy = jest.fn() + + @withWeapp({ + ready () { + this.triggerEvent('fuck', 'a', 'b', 'c') + } + }) + class A extends TaroComponent { + render () { + return
{this.data.a}
+ } + } + + @withWeapp({ + fuck (...args) { + spy(...args) + } + }) + class B extends TaroComponent { + render () { + return
+ } + } + + render(, scratch) + + expect(spy).toBeCalledWith('a', 'b', 'c') + }) +}) diff --git a/packages/taro-with-weapp/__tests__/state.jsx b/packages/taro-with-weapp/__tests__/state.jsx new file mode 100644 index 000000000000..1dce119e5c65 --- /dev/null +++ b/packages/taro-with-weapp/__tests__/state.jsx @@ -0,0 +1,114 @@ +/** @jsx createElement */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/camelcase */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { createElement, render } from 'nervjs' +import withWeapp from '../src' +import { TaroComponent, delay } from './utils' + +describe('lifecycle', () => { + /** + * @type {Element} scratch + */ + let scratch + + beforeEach(() => { + scratch = document.createElement('div') + }) + + test('state can destruct from this', (done) => { + @withWeapp({ + data: { + a: 'a' + } + }) + class A extends TaroComponent { + render () { + return
{this.a}
+ } + } + + render(
, scratch) + + delay(() => { + expect(scratch.textContent).toBe('a') + done() + }) + }) + + test('state can be changed by this.setData', (done) => { + @withWeapp({ + data: { + a: '' + }, + ready () { + this.setData({ + a: 'b' + }) + } + }) + class A extends TaroComponent { + render () { + return
{this.a}
+ } + } + + render(
, scratch) + + delay(() => { + expect(scratch.textContent).toBe('b') + done() + }) + }) + + test('state can be changed by this.setData path', (done) => { + @withWeapp({ + data: { + a: { + b: '' + } + }, + ready () { + this.setData({ + 'a.b': 'b' + }) + } + }) + class A extends TaroComponent { + render () { + return
{this.a.b}
+ } + } + + render(
, scratch) + + delay(() => { + expect(scratch.textContent).toBe('b') + done() + }) + }) + + test('state can be changed even not init data is provided', (done) => { + @withWeapp({ + data: { + }, + ready () { + this.setData({ + 'a': 'b' + }) + } + }) + class A extends TaroComponent { + render () { + return
{this.data.a}
+ } + } + + render(
, scratch) + + delay(() => { + expect(scratch.textContent).toBe('b') + done() + }) + }) +}) diff --git a/packages/taro-with-weapp/__tests__/utils.js b/packages/taro-with-weapp/__tests__/utils.js new file mode 100644 index 000000000000..65cecab6598f --- /dev/null +++ b/packages/taro-with-weapp/__tests__/utils.js @@ -0,0 +1,17 @@ +import { Component } from 'nervjs' + +export class TaroComponent extends Component { + $router = { + params: { + a: 1 + } + } + + $scope = {} +} + +export const delay = (fn) => { + setTimeout(() => { + fn() + }, 0) +} diff --git a/packages/taro-with-weapp/jest.config.js b/packages/taro-with-weapp/jest.config.js new file mode 100644 index 000000000000..07d5515634e5 --- /dev/null +++ b/packages/taro-with-weapp/jest.config.js @@ -0,0 +1,31 @@ +const { jsWithTs: tsjPreset } = require('ts-jest/presets') + +module.exports = { + 'testEnvironment': 'jsdom', + 'transform': { + ...tsjPreset.transform + }, + 'testURL': 'http://localhost/', + 'moduleFileExtensions': [ + 'ts', + 'tsx', + 'js', + 'jsx', + 'json', + 'node' + ], + 'globals': { + 'ts-jest': { + 'diagnostics': false, + 'tsConfig': { + 'jsx': 'react', + 'allowJs': true, + 'target': 'ES6' + } + } + }, + 'testPathIgnorePatterns': [ + 'node_modules', + 'utils' + ] +} diff --git a/packages/taro-with-weapp/package.json b/packages/taro-with-weapp/package.json index c7da7176ee00..d06db2de51bb 100644 --- a/packages/taro-with-weapp/package.json +++ b/packages/taro-with-weapp/package.json @@ -29,18 +29,19 @@ "@types/jest": "^22.2.3", "@types/lodash": "^4.14.105", "@types/node": "^9.6.2", - "jest": "^23.0.1", + "@types/sinon": "^7.0.13", + "babel-jest": "^23.6.0", + "jest": "^23.6.0", "jest-cli": "^22.1.4", + "nervjs": "^1.4.4", "rollup": "^0.66.2", "rollup-plugin-buble": "^0.19.6", "rollup-plugin-typescript2": "^0.17.0", - "ts-jest": "^22.4.6", + "sinon": "^7.4.1", + "ts-jest": "^23.10.5", "tslint": "^5.10.0", "tslint-config-prettier": "^1.10.0", "tslint-config-standard": "^7.0.0", "typescript": "^3.0.1" - }, - "publishConfig": { - "access": "public" } } diff --git a/packages/taro-with-weapp/src/index.ts b/packages/taro-with-weapp/src/index.ts index 9a0d4736683f..edebe17c4c2b 100644 --- a/packages/taro-with-weapp/src/index.ts +++ b/packages/taro-with-weapp/src/index.ts @@ -20,7 +20,7 @@ interface WxOptions { methods?: { [key: string]: Function; } - properties?: Record + properties?: Record | Function> props?: Record data?: Record } @@ -73,7 +73,7 @@ export default function withWeapp (weappConf: WxOptions) { for (const propKey in props) { if (props.hasOwnProperty(propKey)) { const propValue = props[propKey] - if (isFunction(propValue)) { + if (!isFunction(propValue)) { if (propValue.observer) { this._observeProps.push({ name: propKey, @@ -93,7 +93,7 @@ export default function withWeapp (weappConf: WxOptions) { case 'externalClasses': break case 'data': - this.state = confKey + this.state = confValue const keys = Object.keys(this.state) let i = keys.length while (i--) { @@ -200,16 +200,6 @@ export default function withWeapp (weappConf: WxOptions) { } public componentWillMount () { - this.safeExecute(super.componentWillMount) - this.executeLifeCycles(this.willMounts, this.$router.params || {}) - } - - public componentDidMount () { - this.safeExecute(super.componentDidMount) - this.executeLifeCycles(this.didMounts) - } - - public componentWillUnmount () { this._observeProps.forEach(({ name: key, observer }) => { const prop = this.props[key] if (typeof observer === 'string') { @@ -221,6 +211,16 @@ export default function withWeapp (weappConf: WxOptions) { observer.call(this, prop, prop, key) } }) + this.safeExecute(super.componentWillMount) + this.executeLifeCycles(this.willMounts, this.$router.params || {}) + } + + public componentDidMount () { + this.safeExecute(super.componentDidMount) + this.executeLifeCycles(this.didMounts) + } + + public componentWillUnmount () { this.safeExecute(super.componentWillUnmount) this.executeLifeCycles(this.willUnmounts) } @@ -255,6 +255,28 @@ export default function withWeapp (weappConf: WxOptions) { } } + const props = weappConf['properties'] + + if (props) { + for (const propKey in props) { + const propValue = props[propKey] + if (propValue != null && !isFunction(propValue)) { + if (propValue.value !== undefined) { // 如果是 null 也赋值到 defaultProps + BaseComponent.defaultProps = { + [propKey]: propValue.value, + ...BaseComponent.defaultProps + } + } + } + } + } + + const externalClasses = weappConf['externalClasses'] + + if (externalClasses) { + BaseComponent.externalClasses = externalClasses + } + return BaseComponent } }