diff --git a/.travis.yml b/.travis.yml index 30b0ddd4ec..1049bc9de3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ cache: yarn script: - npm run danger - npm test + - npm run test-preact - coveralls < ./coverage/lcov.info || true # ignore coveralls error - npm run compile - npm run filesize diff --git a/jest.preact.config.json b/jest.preact.config.json new file mode 100644 index 0000000000..22eeaa629e --- /dev/null +++ b/jest.preact.config.json @@ -0,0 +1,24 @@ +{ + "testEnvironment": "jsdom", + "transform": { + "^.+\\.tsx?$": "ts-jest", + "^.+\\.jsx?$": "babel-jest" + }, + "mapCoverage": true, + "moduleFileExtensions": ["ts", "tsx", "js", "json"], + "modulePathIgnorePatterns": [ + "/examples", + "/test/flow-usage.js", + "/test/typescript-usage.tsx", + "/test/client/", + "/test/parser.test.ts/", + "/test/test-utils.test.tsx" + ], + "projects": [""], + "testRegex": "(/test/(?!test-utils\b)\b.*|\\.(test|spec))\\.(ts|tsx|js)$", + "moduleNameMapper": { + "^react$": "preact-compat", + "react-dom$": "preact-compat", + "react-dom/server": "preact-compat/server" + } +} diff --git a/package.json b/package.json index b92df2d9ea..352718daab 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "test": "npm run lint && npm run type-check && npm run jest", "jest": "jest --maxWorkers=4 --coverage", "test-watch": "jest --watch", + "test-preact": "jest --config ./jest.preact.config.json --no-cache", "filesize": "bundlesize", "type-check": "tsc --project tsconfig.json --noEmit && flow check", "precompile": "rimraf lib && rimraf npm", @@ -100,6 +101,8 @@ "jsdom": "11.5.1", "lint-staged": "6.0.0", "pre-commit": "1.2.2", + "preact": "^8.2.7", + "preact-compat": "^3.17.0", "prettier": "1.10.2", "react": "16.2.0", "react-dom": "16.2.0", diff --git a/src/getDataFromTree.ts b/src/getDataFromTree.ts index 935794262a..c9ce72cb4a 100755 --- a/src/getDataFromTree.ts +++ b/src/getDataFromTree.ts @@ -19,6 +19,17 @@ export interface QueryTreeResult { context: Context; } +interface PreactElement { + attributes: any; +} + +function getProps(element: ReactElement | PreactElement): any { + return ( + (element as ReactElement).props || + (element as PreactElement).attributes + ); +} + // Recurse a React Element tree, running visitor on each element. // If visitor returns `false`, don't call the element's render function // or recurse into its child elements @@ -42,19 +53,19 @@ export function walkTree( const Component = element.type; // a stateless functional component or a class if (typeof Component === 'function') { - const props = assign({}, Component.defaultProps, element.props); + const props = assign({}, Component.defaultProps, getProps(element)); let childContext = context; let child; // Are we are a react class? // https://github.com/facebook/react/blob/master/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js#L66 - if (Component.prototype && Component.prototype.isReactComponent) { + if (Component.prototype && Component.prototype.render) { // typescript force casting since typescript doesn't have definitions for class // methods const _component = Component as any; const instance = new _component(props, context); // In case the user doesn't pass these to super in the constructor - instance.props = instance.props || props; + instance.props = instance.props || instance.attributes || props; instance.context = instance.context || context; // set the instance state to null (not undefined) if not set, to match React behaviour instance.state = instance.state || null; diff --git a/src/index.js.flow b/src/index.js.flow index 709e869fd5..28178e22aa 100644 --- a/src/index.js.flow +++ b/src/index.js.flow @@ -87,7 +87,10 @@ export interface OptionProps { mutate: MutationFunc; } -export type OptionDescription

= QueryOpts | MutationOpts | ((props: P) => QueryOpts | MutationOpts); +export type OptionDescription

= + | QueryOpts + | MutationOpts + | ((props: P) => QueryOpts | MutationOpts); export type NamedProps = P & { ownProps: R, diff --git a/test/server/getDataFromTree.test.tsx b/test/server/getDataFromTree.test.tsx index 2c18204d7e..8634d42104 100644 --- a/test/server/getDataFromTree.test.tsx +++ b/test/server/getDataFromTree.test.tsx @@ -90,6 +90,7 @@ describe('SSR', () => { it('functional stateless components with children', () => { let elementCount = 0; + let isPreact = false; interface Props { n: number; children?: React.ReactNode; @@ -106,10 +107,20 @@ describe('SSR', () => { , {}, element => { + if (element && (element as any).preactCompatUpgraded) { + isPreact = true; + } elementCount += 1; }, ); - expect(elementCount).toEqual(9); + // preact does a slightly different pass than react does here + // fwiw, preact's seems to make sense here (7 nodes vs 9) + // XXX verify markup checksums on this + if (isPreact) { + expect(elementCount).toEqual(7); + } else { + expect(elementCount).toEqual(9); + } }); it('functional stateless components with null children', () => { @@ -706,7 +717,14 @@ describe('SSR', () => { it('should correctly initialize an empty state to null', () => { class Element extends React.Component { render() { - expect(this.state).toBeNull(); + // this is a check for how react and preact differ. Preact (nicely) + // comes with a default state + if ((this as any).__d) { + // I'm preact + expect(this.state).toEqual({}); + } else { + expect(this.state).toBeNull(); + } return null; } }