diff --git a/src/child/child.js b/src/child/child.js index a06c95cb..dfc69b75 100644 --- a/src/child/child.js +++ b/src/child/child.js @@ -96,8 +96,8 @@ export type ChildComponent
= {|
init: () => ZalgoPromise (
- options: NormalizedComponentOptionsType
+export function childComponent (
+ options: NormalizedComponentOptionsType
): ChildComponent {
const { tag, propsDef, autoResize, allowedParentDomains } = options;
diff --git a/src/component/component.js b/src/component/component.js
index c86e0601..fa375718 100644
--- a/src/component/component.js
+++ b/src/component/component.js
@@ -29,6 +29,7 @@ import {
import { childComponent, type ChildComponent } from "../child";
import {
+ type ParentComponent,
type RenderOptionsType,
type ParentHelpers,
parentComponent,
@@ -106,9 +107,10 @@ export type ExportsDefinition = {|
+export type ComponentOptionsType = {|
tag: string,
+ getExtensions?: (parent: ParentComponent ) => ExtType,
url: string | (({| props: PropsType |}) => string),
domain?: DomainMatcher,
bridgeUrl?: string,
@@ -155,10 +157,11 @@ type AutoResizeType = {|
element?: string,
|};
-export type NormalizedComponentOptionsType = {|
+export type NormalizedComponentOptionsType = {|
tag: string,
name: string,
+ getExtensions: (parent: ParentComponent ) => ExtType,
url: string | (({| props: PropsType |}) => string),
domain: ?DomainMatcher,
bridgeUrl: ?string,
@@ -192,12 +195,13 @@ export type NormalizedComponentOptionsType = {|
exports: ExportsMapperDefinition = {|
+export type ZoidComponentInstance = {|
+ ...ExtType,
...ParentHelpers ,
...X,
...C,
isEligible: () => boolean,
- clone: () => ZoidComponentInstance ,
+ clone: () => ZoidComponentInstance ,
render: (
container?: ContainerReferenceType,
context?: $Values = {|
|};
// eslint-disable-next-line flowtype/require-exact-type
-export type ZoidComponent = {
- (props?: PropsInputType | void): ZoidComponentInstance ,
+export type ZoidComponent = {
+ (props?: PropsInputType | void): ZoidComponentInstance ,
// eslint-disable-next-line no-undef
driver: ,
canRenderTo: (CrossDomainWindowType) => ZalgoPromise (
- options: ComponentOptionsType
-): NormalizedComponentOptionsType {
+function getDefaultGetExtensions (): (
+ parent: ParentComponent
+) => ExtType {
+ return function getExtensions(): ExtType {
+ // $FlowFixMe
+ const ext: ExtType = {};
+ return ext;
+ };
+}
+
+function normalizeOptions (
+ options: ComponentOptionsType
+): NormalizedComponentOptionsType {
const {
tag,
url,
domain,
bridgeUrl,
props = {},
+ getExtensions = getDefaultGetExtensions (),
dimensions = getDefaultDimensions(),
autoResize = getDefaultAutoResize(),
allowedParentDomains = WILDCARD,
@@ -329,31 +344,42 @@ function normalizeOptions (
eligible,
children,
exports: xports,
+ getExtensions,
};
}
let cleanInstances = cleanup();
const cleanZoid = cleanup();
-export type Component = {|
- init: (props?: PropsInputType | void) => ZoidComponentInstance ,
- instances: $ReadOnlyArray = {|
+ init: (
+ props?: PropsInputType | void
+ ) => ZoidComponentInstance ,
+ instances: $ReadOnlyArray ,
|};
-export function component (
- opts: ComponentOptionsType
-): Component {
+export function component (
+ opts: ComponentOptionsType
+): Component {
if (__DEBUG__) {
validateOptions(opts);
}
const options = normalizeOptions(opts);
- const { name, tag, defaultContext, propsDef, eligible, children } = options;
+ const {
+ name,
+ tag,
+ defaultContext,
+ propsDef,
+ eligible,
+ children,
+ getExtensions,
+ } = options;
const global = getGlobal(window);
const driverCache = {};
@@ -471,7 +497,7 @@ export function component (
const init = (
inputProps?: PropsInputType | void
- ): ZoidComponentInstance => {
+ ): ZoidComponentInstance => {
// eslint-disable-next-line prefer-const
let instance;
@@ -590,6 +616,7 @@ export function component (
};
instance = {
+ ...getExtensions(parent),
...parent.getExports(),
...parent.getHelpers(),
...getChildren(),
@@ -663,15 +690,15 @@ export type ComponentDriverType = {|
export type ZoidProps = PropsType ;
// eslint-disable-next-line no-undef
-export type CreateZoidComponent = (
+export type CreateZoidComponent = (
// eslint-disable-next-line no-undef
- options: ComponentOptionsType
+ options: ComponentOptionsType
// eslint-disable-next-line no-undef
-) => ZoidComponent ;
+) => ZoidComponent ;
-export const create: CreateZoidComponent = (
- options: ComponentOptionsType
-): ZoidComponent => {
+export const create: CreateZoidComponent = (
+ options: ComponentOptionsType
+): ZoidComponent => {
setupPostRobot();
const comp = component(options);
diff --git a/src/component/validate.js b/src/component/validate.js
index 98fdf77f..59c251c4 100644
--- a/src/component/validate.js
+++ b/src/component/validate.js
@@ -6,8 +6,8 @@ import { CONTEXT, PROP_TYPE } from "../constants";
import type { ComponentOptionsType } from "./index";
-function validatepropsDefinitions (
- options: ComponentOptionsType
+function validatepropsDefinitions (
+ options: ComponentOptionsType
) {
if (options.props && !(typeof options.props === "object")) {
throw new Error(`Expected options.props to be an object`);
@@ -49,8 +49,8 @@ function validatepropsDefinitions (
}
// eslint-disable-next-line complexity
-export function validateOptions (
- options: ?ComponentOptionsType
+export function validateOptions (
+ options: ?ComponentOptionsType
) {
// eslint-ignore-line
diff --git a/src/parent/parent.js b/src/parent/parent.js
index 070c8f9c..815d47f4 100644
--- a/src/parent/parent.js
+++ b/src/parent/parent.js
@@ -254,7 +254,7 @@ type RenderOptions = {|
rerender: Rerender,
|};
-type ParentComponent = {|
+export type ParentComponent = {|
init: () => void,
render: (RenderOptions) => ZalgoPromise ,
@@ -271,19 +271,19 @@ const getDefaultOverrides = (): ParentDelegateOverrides => {
return {};
};
-type ParentOptions = {|
+type ParentOptions = {|
uid: string,
- options: NormalizedComponentOptionsType ,
+ options: NormalizedComponentOptionsType ,
overrides?: ParentDelegateOverrides ,
parentWin?: CrossDomainWindowType,
|};
-export function parentComponent ({
+export function parentComponent ({
uid,
options,
overrides = getDefaultOverrides(),
parentWin = window,
-}: ParentOptions ): ParentComponent {
+}: ParentOptions ): ParentComponent {
const {
propsDef,
containerTemplate,
diff --git a/test/tests/extensions.jsx b/test/tests/extensions.jsx
new file mode 100644
index 00000000..96f179cd
--- /dev/null
+++ b/test/tests/extensions.jsx
@@ -0,0 +1,175 @@
+/* @flow */
+/** @jsx node */
+
+import { wrapPromise } from "@krakenjs/belter/src";
+import { getParent } from "@krakenjs/cross-domain-utils/src";
+
+import { onWindowOpen, getBody } from "../common";
+import { zoid } from "../zoid";
+
+describe("zoid getExtensions cases", () => {
+ it("should render a component with additional properties to the component", () => {
+ return wrapPromise(({ expect }) => {
+ window.__component__ = () => {
+ return zoid.create({
+ tag: "test-render-with-extended-properties",
+ url: () => "mock://www.child.com/base/test/windows/child/index.htm",
+ domain: "mock://www.child.com",
+ getExtensions: () => {
+ return {
+ testAttribute: "123",
+ testMethod: () => "result_of_test_method",
+ };
+ },
+ });
+ };
+
+ onWindowOpen().then(
+ expect("onWindowOpen", ({ win }) => {
+ if (getParent(win) !== window) {
+ throw new Error(`Expected window parent to be current window`);
+ }
+ })
+ );
+
+ const component = window.__component__();
+ const instance = component({
+ onRendered: expect("onRendered"),
+ });
+
+ if (instance.testAttribute !== "123") {
+ throw new Error(
+ `Expected test attribute to be "123", but got ${instance.testAttribute}`
+ );
+ }
+ if (instance.testMethod() !== "result_of_test_method") {
+ throw new Error(
+ `Expected test attribute to be "result_of_test_method", but got ${instance.testMethod()}`
+ );
+ }
+
+ return instance.render(getBody());
+ });
+ });
+
+ it("should not be able to ovverride default properties of component using getExtensions", () => {
+ return wrapPromise(({ expect }) => {
+ window.__component__ = () => {
+ return zoid.create({
+ tag: "test-extended-properties-cannot-override-defaults",
+ url: () => "mock://www.child.com/base/test/windows/child/index.htm",
+ domain: "mock://www.child.com",
+ getExtensions: () => {
+ return {
+ render: () => {
+ throw new Error("The render method should have been ovveriden");
+ },
+ };
+ },
+ });
+ };
+
+ onWindowOpen().then(
+ expect("onWindowOpen", ({ win }) => {
+ if (getParent(win) !== window) {
+ throw new Error(`Expected window parent to be current window`);
+ }
+ })
+ );
+
+ const component = window.__component__();
+ const instance = component({
+ onRendered: expect("onRendered"),
+ });
+
+ return instance.render(getBody());
+ });
+ });
+
+ it("should be able to interact with method supplied to the component during instantiation", () => {
+ return wrapPromise(({ expect }) => {
+ window.__component__ = () => {
+ return zoid.create({
+ tag: "test-render-with-extended-method-invokes-callbacks",
+ url: () => "mock://www.child.com/base/test/windows/child/index.htm",
+ domain: "mock://www.child.com",
+ getExtensions: (parent) => {
+ return {
+ invokeCalculatePrice: () => {
+ return parent.getProps().calculatePrice();
+ },
+ };
+ },
+ });
+ };
+
+ onWindowOpen().then(
+ expect("onWindowOpen", ({ win }) => {
+ if (getParent(win) !== window) {
+ throw new Error(`Expected window parent to be current window`);
+ }
+ })
+ );
+
+ const component = window.__component__();
+ const instance = component({
+ onRendered: expect("onRendered"),
+ calculatePrice: () => 1034.56,
+ });
+
+ if (instance.invokeCalculatePrice() !== 1034.56) {
+ throw new Error(
+ `Expected test attribute to be "1034.56", but got ${instance.invokeCalculatePrice()}`
+ );
+ }
+
+ return instance.render(getBody());
+ });
+ });
+
+ it("should be able to interact with updated methods supplied to the component during instantiation", () => {
+ return wrapPromise(({ expect }) => {
+ window.__component__ = () => {
+ return zoid.create({
+ tag: "test-render-with-updated-extended-method-invokes-callbacks",
+ url: () => "mock://www.child.com/base/test/windows/child/index.htm",
+ domain: "mock://www.child.com",
+ getExtensions: (parent) => {
+ return {
+ invokeCalculatePrice: () => {
+ return parent.getProps().calculatePrice();
+ },
+ };
+ },
+ });
+ };
+
+ onWindowOpen().then(
+ expect("onWindowOpen", ({ win }) => {
+ if (getParent(win) !== window) {
+ throw new Error(`Expected window parent to be current window`);
+ }
+ })
+ );
+
+ const component = window.__component__();
+
+ const instance = component({
+ onRendered: expect("onRendered"),
+ calculatePrice: () => 1034.56,
+ });
+
+ instance.updateProps({
+ calculatePrice: () => 50.56,
+ });
+
+ if (instance.invokeCalculatePrice() !== 50.56) {
+ throw new Error(
+ `Expected test attribute to be "50.56", but got ${instance.invokeCalculatePrice()}`
+ );
+ }
+
+ return instance.render(getBody());
+ });
+ });
+});
diff --git a/test/tests/index.js b/test/tests/index.js
index 029947ba..ffb02e8d 100644
--- a/test/tests/index.js
+++ b/test/tests/index.js
@@ -20,3 +20,4 @@ import "./remove";
import "./rerender";
import "./method";
import "./children";
+import "./extensions";