diff --git a/jest.config.js b/jest.config.js
index 4ff55ca..26911f2 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -26,6 +26,7 @@ function project(displayName, config) {
transform: {
"\\.ts$": "ts-jest"
},
- coveragePathIgnorePatterns: ["/__tests__/"]
+ coveragePathIgnorePatterns: ["/__tests__/"],
+ transformIgnorePatterns: ["node_modules/(?!(marko)/)"]
};
}
diff --git a/package-lock.json b/package-lock.json
index 4154cac..eb21da7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -962,6 +962,16 @@
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
+ "async-writer": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/async-writer/-/async-writer-2.1.3.tgz",
+ "integrity": "sha1-XCo4B8u3GwfSXX4kAhMnKvAwoo8=",
+ "dev": true,
+ "requires": {
+ "complain": "^1.0.0",
+ "events": "^1.0.2"
+ }
+ },
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -5018,6 +5028,16 @@
}
}
},
+ "marko-widgets": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/marko-widgets/-/marko-widgets-7.0.1.tgz",
+ "integrity": "sha1-Dg3ytWCfRi9uk/UvhihTXy9LwD8=",
+ "dev": true,
+ "requires": {
+ "raptor-dom": "^1.1.0",
+ "raptor-renderer": "^1.4.4"
+ }
+ },
"matcher": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/matcher/-/matcher-1.1.1.tgz",
@@ -5825,6 +5845,15 @@
"integrity": "sha1-uDw8m2A9yYXCw6n3jStAc+b2Akw=",
"dev": true
},
+ "raptor-dom": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/raptor-dom/-/raptor-dom-1.1.1.tgz",
+ "integrity": "sha1-Xt4wpy3A+ZeiwXMfwfLESz+1rFo=",
+ "dev": true,
+ "requires": {
+ "raptor-pubsub": "^1.0.5"
+ }
+ },
"raptor-json": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/raptor-json/-/raptor-json-1.1.0.tgz",
@@ -5858,12 +5887,50 @@
}
}
},
+ "raptor-pubsub": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/raptor-pubsub/-/raptor-pubsub-1.0.5.tgz",
+ "integrity": "sha1-Fe4SoOUFnsFbcv+dYeyvn0IS70c=",
+ "dev": true,
+ "requires": {
+ "events": "^1.0.2",
+ "raptor-util": "^1.0.0"
+ },
+ "dependencies": {
+ "raptor-util": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/raptor-util/-/raptor-util-1.1.2.tgz",
+ "integrity": "sha1-8u6AdqmuPq4uZWcuRqIgB0+i3/M=",
+ "dev": true
+ }
+ }
+ },
"raptor-regexp": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/raptor-regexp/-/raptor-regexp-1.0.1.tgz",
"integrity": "sha1-7PD2bGZxwM2fXkjDcFAmxVCZlcA=",
"dev": true
},
+ "raptor-renderer": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/raptor-renderer/-/raptor-renderer-1.5.0.tgz",
+ "integrity": "sha512-Bf6aIh/80cjFz9+xJ//5aV/rIDbqMNYZOsCxgXbXJfNLGjGPO4wn9nixdSASl/Pl3wCUTIixU542qjbCQBRYvQ==",
+ "dev": true,
+ "requires": {
+ "async-writer": "^2.0.0",
+ "raptor-dom": "^1.0.0",
+ "raptor-pubsub": "^1.0.2",
+ "raptor-util": "^1.0.0"
+ },
+ "dependencies": {
+ "raptor-util": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/raptor-util/-/raptor-util-1.1.2.tgz",
+ "integrity": "sha1-8u6AdqmuPq4uZWcuRqIgB0+i3/M=",
+ "dev": true
+ }
+ }
+ },
"raptor-strings": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/raptor-strings/-/raptor-strings-1.0.2.tgz",
diff --git a/package.json b/package.json
index d42d4d1..9907e0e 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"jest-dom": "^3.5.0",
"lint-staged": "^8.2.1",
"marko": "^4.18.2",
+ "marko-widgets": "^7.0.1",
"prettier": "^1.18.2",
"ts-jest": "^24.0.2",
"tslint": "^5.18.0",
diff --git a/src/__tests__/fixtures/legacy-counter/index.js b/src/__tests__/fixtures/legacy-counter/index.js
new file mode 100644
index 0000000..c4f8fb2
--- /dev/null
+++ b/src/__tests__/fixtures/legacy-counter/index.js
@@ -0,0 +1,10 @@
+module.exports = require("marko-widgets").defineComponent({
+ template: require("./template.marko"),
+ getInitialState() {
+ return { count: 0 };
+ },
+
+ increment() {
+ this.setState("count", this.state.count + 1);
+ }
+});
diff --git a/src/__tests__/fixtures/legacy-counter/template.marko b/src/__tests__/fixtures/legacy-counter/template.marko
new file mode 100644
index 0000000..4261d3b
--- /dev/null
+++ b/src/__tests__/fixtures/legacy-counter/template.marko
@@ -0,0 +1,4 @@
+
+ Value: ${data.count}
+
+
\ No newline at end of file
diff --git a/src/__tests__/render.browser.ts b/src/__tests__/render.browser.ts
index 5fd3013..3d2eb57 100644
--- a/src/__tests__/render.browser.ts
+++ b/src/__tests__/render.browser.ts
@@ -1,6 +1,7 @@
import "jest-dom/extend-expect";
import { render, fireEvent, wait, cleanup } from "..";
import Counter from "./fixtures/counter.marko";
+import LegacyCounter from "./fixtures/legacy-counter";
import UpdateCounter from "./fixtures/update-counter.marko";
import HelloWorld from "./fixtures/hello-world.marko";
import HelloName from "./fixtures/hello-name.marko";
@@ -17,6 +18,15 @@ test("renders interactive content in the document", async () => {
await wait(() => expect(getByText("Value: 1")));
});
+test("renders interactive content in the document with Marko 3", async () => {
+ const { getByText } = await render(LegacyCounter);
+ expect(getByText(/Value: 0/)).toBeInTheDocument();
+
+ fireEvent.click(getByText("Increment"));
+
+ await wait(() => expect(getByText("Value: 1")));
+});
+
test("can be rerendered with new input", async () => {
const { getByText, rerender } = await render(HelloName, { name: "Michael" });
diff --git a/src/__tests__/render.server.ts b/src/__tests__/render.server.ts
index e48fcd5..1477f2f 100644
--- a/src/__tests__/render.server.ts
+++ b/src/__tests__/render.server.ts
@@ -1,5 +1,6 @@
import { render, fireEvent } from "..";
import Counter from "./fixtures/counter.marko";
+import LegacyCounter from "./fixtures/legacy-counter";
import Clickable from "./fixtures/clickable.marko";
import HelloName from "./fixtures/hello-name.marko";
@@ -11,6 +12,14 @@ test("renders static content in a document without a browser context", async ()
);
});
+test("renders static content from a Marko 3 component", async () => {
+ const { getByText } = await render(LegacyCounter);
+ expect(getByText("Value: 0")).toHaveProperty(
+ ["ownerDocument", "defaultView"],
+ null
+ );
+});
+
test("fails when rerendering", async () => {
const { rerender } = await render(HelloName, { name: "Michael" });
await expect(rerender({ name: "Dylan" })).rejects.toThrow(/cannot re-render/);
diff --git a/src/index-browser.ts b/src/index-browser.ts
index 7487ec3..ccac75f 100644
--- a/src/index-browser.ts
+++ b/src/index-browser.ts
@@ -20,8 +20,17 @@ export async function render(
container = document.body.appendChild(document.createElement("div"))
} = options;
- const result = await template.render(input);
- const component = result.appendTo(container).getComponent();
+ // Doesn't use promise API so that we can support Marko v3
+ const renderResult = (await new Promise((resolve, reject) =>
+ template.render(input, (err, result) =>
+ err ? /* istanbul ignore next */ reject(err) : resolve(result)
+ )
+ )) as any;
+
+ const isV3 = !renderResult.getComponent;
+ const component = renderResult
+ .appendTo(container)
+ [isV3 ? /* istanbul ignore next */ "getWidget" : "getComponent"]();
const eventRecord: EventRecord = {};
mountedComponents.add({ container, component });
@@ -63,12 +72,23 @@ export async function render(
},
rerender(newInput?: typeof input): Promise {
return new Promise(resolve => {
- component.once("update", () => resolve());
-
- if (newInput) {
- component.input = newInput;
+ /* istanbul ignore if */
+ if (isV3) {
+ component.once("render", () => resolve());
+
+ if (newInput) {
+ component.setProps(newInput);
+ } else {
+ component.setStateDirty("__forceUpdate__");
+ }
} else {
- component.forceUpdate();
+ component.once("update", () => resolve());
+
+ if (newInput) {
+ component.input = newInput;
+ } else {
+ component.forceUpdate();
+ }
}
});
},
@@ -85,13 +105,17 @@ export async function render(
}
},
...within((container as any) as HTMLElement)
- };
+ } as const;
}
export function cleanup() {
mountedComponents.forEach(destroyComponent);
}
+export type RenderResult = Parameters<
+ NonNullable["then"]>[0]>
+>[0];
+
function destroyComponent({ container, component }) {
component.destroy();
diff --git a/src/index.ts b/src/index.ts
index e16b125..2e5c1b0 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,10 +11,18 @@ export * from "@testing-library/dom";
export async function render(
template: T,
- input: Parameters[0] = {},
+ input: Parameters[0] = {},
options?: RenderOptions
) {
- const html = String(await template.render(input));
+ // Doesn't use promise API so that we can support Marko v3
+ const renderMethod = template.renderToString ? "renderToString" : "render";
+ const html = String(
+ await new Promise((resolve, reject) =>
+ template[renderMethod](input, (err, result) =>
+ err ? /* istanbul ignore next */ reject(err) : resolve(result)
+ )
+ )
+ );
const container = JSDOM.fragment(html);
(container as any).outerHTML = html; // Fixes prettyDOM for container
@@ -43,8 +51,12 @@ export async function render(
}
},
...within((container as any) as HTMLElement)
- };
+ } as const;
}
/* istanbul ignore next: There is no cleanup for SSR. */
export function cleanup() {}
+
+export type RenderResult = Parameters<
+ NonNullable["then"]>[0]>
+>[0];
diff --git a/src/types.ts b/src/types.ts
index e2e6b61..1fbb392 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -8,7 +8,11 @@ export interface RenderOptions {
}
export interface Template {
- render(input: unknown): Promise;
+ renderToString(
+ input: unknown,
+ cb: (err: Error | null, result: any) => void
+ ): any;
+ render(input: unknown, cb: (err: Error | null, result: any) => void): any;
}
export const INTERNAL_EVENTS = [