Skip to content

Commit

Permalink
Merge pull request #186 from airbnb/refactor-template-reuse
Browse files Browse the repository at this point in the history
Refactor template reuse & fix `getSubtreeId`
  • Loading branch information
malash authored Nov 26, 2022
2 parents c875d32 + 52d8bc5 commit 7dc5788
Show file tree
Hide file tree
Showing 20 changed files with 266 additions and 241 deletions.
11 changes: 5 additions & 6 deletions packages/core/src/components/subtree.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import React, { createElement, Fragment, CSSProperties } from 'react';
import { TYPE_SUBTREE, GOJI_TARGET } from '../constants';
import { TYPE_SUBTREE } from '../constants';

export const useSubtree = GOJI_TARGET === 'wechat' || GOJI_TARGET === 'qq';
export const useSubtree = process.env.GOJI_TARGET === 'wechat' || process.env.GOJI_TARGET === 'qq';

export const subtreeMaxDepth = (process.env.GOJI_MAX_DEPTH as any as number) ?? 10;
export const subtreeMaxDepthFromConfig = (process.env.GOJI_MAX_DEPTH as any as number) ?? 10;

export const Subtree = ({
unsafe_className: className,
unsafe_style: style,
...restProps
}: // eslint-disable-next-line camelcase
React.PropsWithChildren<{ unsafe_className?: string; unsafe_style?: CSSProperties }>) => {
}: React.PropsWithChildren<{ unsafe_className?: string; unsafe_style?: CSSProperties }>) => {
if (useSubtree) {
return createElement(useSubtree ? TYPE_SUBTREE : Fragment, {
return createElement(TYPE_SUBTREE, {
className,
style,
...restProps,
Expand Down
2 changes: 0 additions & 2 deletions packages/core/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ export const TYPE_SUBTREE = 'GOJI_TYPE_SUBTREE';

export type GojiTarget = 'wechat' | 'baidu' | 'alipay' | 'toutiao' | 'qq' | 'toutiao';

export const GOJI_TARGET: GojiTarget = (process.env.GOJI_TARGET as GojiTarget) || 'wechat';

export interface SimplifyComponent {
name: string;
properties: Array<string>;
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ReactNode } from 'react';
import './patchGlobalObject';
import { createAdaptor, AdaptorType, ExportComponentMeta } from './adaptor';
import { GOJI_TARGET } from './constants';
import { GojiTarget } from './constants';
import { batchedUpdates } from './reconciler';

interface RenderOptions {
Expand All @@ -21,7 +21,12 @@ export const render = (element: ReactNode, options: Partial<RenderOptions> = {})
...DEFAULT_RENDER_OPTIONS,
...options,
};
const adaptor = createAdaptor(type, GOJI_TARGET, exportMeta, disablePageSharing);
const adaptor = createAdaptor(
type,
process.env.GOJI_TARGET as GojiTarget,
exportMeta,
disablePageSharing,
);
return adaptor.run(element);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { ElementInstance } from '../instance';
import { Container } from '../../container';
import { GOJI_VIRTUAL_ROOT, TYPE_SUBTREE } from '../../constants';
import { TestingAdaptorInstance } from '../../__tests__/helpers/adaptor';

jest.mock('../../components/subtree', () => ({
useSubtree: false,
subtreeMaxDepthFromConfig: 5,
}));

beforeAll(() => {
// @ts-expect-error
process.env.GOJI_WRAPPED_COMPONENTS = [];
});
const view = () => new ElementInstance('view', {}, [], new Container(new TestingAdaptorInstance()));
const subtree = () =>
new ElementInstance(TYPE_SUBTREE, {}, [], new Container(new TestingAdaptorInstance()));
/**
* linkElements
* call `setParent` and `children.push` for given elements
* [a, b, c] => a <- b <- c
*/
const linkElements = (elements: Array<ElementInstance>) => {
const mockContainer = new Container(new TestingAdaptorInstance());
const mockRoot = new ElementInstance(GOJI_VIRTUAL_ROOT, {}, [], mockContainer);
elements[0].setParent(mockRoot);
for (let i = 0; i < elements.length - 1; i += 1) {
elements[i].appendChild(elements[i + 1]);
}
};

describe('no subtree', () => {
test('subtree id should be always undefined', () => {
let leaf: ElementInstance;
const elements = [
view(),
view(),
subtree(),
view(),
view(),
view(),
view(),
view(),
view(),
(leaf = view()),
];
linkElements(elements);
expect(leaf.getSubtreeId()).toBe(undefined);
});
});
74 changes: 74 additions & 0 deletions packages/core/src/reconciler/__tests__/getSubtreeId.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ElementInstance } from '../instance';
import { Container } from '../../container';
import { GOJI_VIRTUAL_ROOT, TYPE_SUBTREE } from '../../constants';
import { TestingAdaptorInstance } from '../../__tests__/helpers/adaptor';

jest.mock('../../components/subtree', () => ({
useSubtree: true,
subtreeMaxDepthFromConfig: 5,
}));

beforeAll(() => {
// @ts-expect-error
process.env.GOJI_WRAPPED_COMPONENTS = [];
});
const view = () => new ElementInstance('view', {}, [], new Container(new TestingAdaptorInstance()));
const subtree = () =>
new ElementInstance(TYPE_SUBTREE, {}, [], new Container(new TestingAdaptorInstance()));
/**
* linkElements
* call `setParent` and `children.push` for given elements
* [a, b, c] => a <- b <- c
*/
const linkElements = (elements: Array<ElementInstance>) => {
const mockContainer = new Container(new TestingAdaptorInstance());
const mockRoot = new ElementInstance(GOJI_VIRTUAL_ROOT, {}, [], mockContainer);
elements[0].setParent(mockRoot);
for (let i = 0; i < elements.length - 1; i += 1) {
elements[i].appendChild(elements[i + 1]);
}
};

describe('use subtree', () => {
test('container works', () => {
let leaf: ElementInstance;
const elements = [view(), view(), (leaf = view())];
linkElements(elements);
expect(leaf.getSubtreeId()).toBe(undefined);
});

test('auto subtree works', () => {
let leaf: ElementInstance;
let sub: ElementInstance;
const elements = [view(), view(), view(), view(), (sub = view()), view(), (leaf = view())];
linkElements(elements);
expect(leaf.getSubtreeId()).toBe(sub.id);
});

test('manual subtree works', () => {
let leaf: ElementInstance;
let sub: ElementInstance;
const elements = [view(), view(), (sub = subtree()), view(), (leaf = view())];
linkElements(elements);
expect(leaf.getSubtreeId()).toBe(sub.id);
});

test('auto subtree inside manual subtree works', () => {
let leaf: ElementInstance;
let sub: ElementInstance;
const elements = [
view(),
view(),
subtree(),
view(),
view(),
view(),
view(),
(sub = view()),
view(),
(leaf = view()),
];
linkElements(elements);
expect(leaf.getSubtreeId()).toBe(sub.id);
});
});
106 changes: 5 additions & 101 deletions packages/core/src/reconciler/__tests__/instance.test.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import React, { useState, createRef } from 'react';
import { ElementInstance, ElementNodeDevelopment, TextNodeDevelopment } from '../instance';
import { Container } from '../../container';
import { TYPE_SUBTREE } from '../../constants';
import { View, gojiEvents } from '../..';
import { act } from '../../testUtils';
import { batchedUpdates } from '..';
import { PublicInstance } from '../publicInstance';
import { render } from '../../__tests__/helpers';
import { TestingAdaptorInstance } from '../../__tests__/helpers/adaptor';

jest.mock('../../../src/components/subtree', () => ({
useSubtree: true,
subtreeMaxDepth: 5,
}));

describe('ElementInstance', () => {
beforeAll(() => {
// @ts-expect-error
process.env.GOJI_WRAPPED_COMPONENTS = [];
});

const instance = new ElementInstance(
'view',
{
Expand Down Expand Up @@ -50,101 +49,6 @@ describe('ElementInstance', () => {
expect((pured as ElementNodeDevelopment).simplifiedId).not.toBeUndefined();
});

describe('getSubtreeId', () => {
beforeAll(() => {
// @ts-expect-error
process.env.GOJI_WRAPPED_COMPONENTS = [];
});
const view = () =>
new ElementInstance('view', {}, [], new Container(new TestingAdaptorInstance()));
const subtree = () =>
new ElementInstance(TYPE_SUBTREE, {}, [], new Container(new TestingAdaptorInstance()));
/**
* linkElements
* call `setParent` and `children.push` for given elements
* [a, b, c] => a <- b <- c
*/
const linkElements = (elements: Array<ElementInstance>) => {
const mockContainer = new Container(new TestingAdaptorInstance());
elements[0].setParent(mockContainer);
for (let i = 0; i < elements.length - 1; i += 1) {
elements[i].appendChild(elements[i + 1]);
}
};

describe('wechat', () => {
beforeAll(() => {
process.env.GOJI_TARGET = 'wechat';
});

test('container works', () => {
let leaf: ElementInstance;
const elements = [view(), view(), (leaf = view())];
linkElements(elements);
expect(leaf.getSubtreeId()).toBe(undefined);
});

test('auto subtree works', () => {
let leaf: ElementInstance;
let sub: ElementInstance;
const elements = [view(), view(), view(), view(), (sub = view()), view(), (leaf = view())];
linkElements(elements);
expect(leaf.getSubtreeId()).toBe(sub.id);
});

test('manual subtree works', () => {
let leaf: ElementInstance;
let sub: ElementInstance;
const elements = [view(), view(), (sub = subtree()), view(), (leaf = view())];
linkElements(elements);
expect(leaf.getSubtreeId()).toBe(sub.id);
});

test('auto subtree inside manual subtree works', () => {
let leaf: ElementInstance;
let sub: ElementInstance;
const elements = [
view(),
view(),
subtree(),
view(),
view(),
view(),
view(),
(sub = view()),
view(),
(leaf = view()),
];
linkElements(elements);
expect(leaf.getSubtreeId()).toBe(sub.id);
});
});

describe('non-wechat', () => {
beforeAll(() => {
process.env.GOJI_TARGET = 'baidu';
});

test('subtree id should be always undefined', () => {
let leaf: ElementInstance;
const elements = [
view(),
view(),
subtree(),
view(),
view(),
view(),
view(),
view(),
view(),
(leaf = view()),
];
linkElements(elements);
expect(leaf.getSubtreeId()).toBe(undefined);
});
});
});

test('batched event handler', () => {
let callback: () => void;
let renderCount = 0;
Expand Down
Loading

0 comments on commit 7dc5788

Please sign in to comment.