Skip to content

Commit ed942b1

Browse files
committed
[IMP] components: make initial component sync if possible
1 parent 8f23fcc commit ed942b1

7 files changed

Lines changed: 106 additions & 119 deletions

File tree

src/runtime/app.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,21 @@ export class App extends TemplateSet {
135135

136136
const fiber = new MountFiber(node, target, options);
137137
this.scheduler.addFiber(fiber);
138-
const prev = getCurrentComputation();
139-
node.initiateRender(fiber);
140-
setComputation(prev);
138+
if (node.willStart.length) {
139+
const prev = getCurrentComputation();
140+
node.initiateRender(fiber);
141+
setComputation(prev);
142+
} else {
143+
node.fiber = fiber;
144+
if (node.mounted.length) {
145+
fiber.root!.mounted.push(fiber);
146+
}
147+
try {
148+
fiber.render();
149+
} catch (e) {
150+
reject(e);
151+
}
152+
}
141153
return promise;
142154
},
143155
destroy: () => {

tests/app/sub_root.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ test("destroy a subroot while another component is mounted in main app", async (
114114

115115
const app = new App();
116116
const comp = await app.createRoot(SomeComponent).mount(fixture);
117-
expect(fixture.innerHTML).toBe("a<div></div>");
118117
await nextTick();
119118
expect(fixture.innerHTML).toBe("a<div>c</div>");
120119
comp.state.flag = true;

tests/components/error_handling.test.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -321,11 +321,12 @@ describe("errors and promises", () => {
321321

322322
const app = new App();
323323
let error: OwlError;
324-
const mountProm = app
325-
.createRoot(Root)
324+
const root = app.createRoot(Root);
325+
const errorProm = nextAppError(app);
326+
const mountProm = root
326327
.mount(fixture)
327328
.catch((e: Error) => (error = e));
328-
await expect(nextAppError(app)).resolves.toThrow(
329+
await expect(errorProm).resolves.toThrow(
329330
"[Owl] Unhandled error. Destroying the root component"
330331
);
331332
await mountProm;
@@ -347,11 +348,12 @@ describe("errors and promises", () => {
347348

348349
const app = new App({ test: true });
349350
let error: OwlError;
350-
const mountProm = app
351-
.createRoot(Root)
351+
const root = app.createRoot(Root);
352+
const errorProm = nextAppError(app);
353+
const mountProm = root
352354
.mount(fixture)
353355
.catch((e: Error) => (error = e));
354-
await expect(nextAppError(app)).resolves.toThrow(
356+
await expect(errorProm).resolves.toThrow(
355357
"[Owl] Unhandled error. Destroying the root component"
356358
);
357359
await mountProm;
@@ -375,11 +377,12 @@ describe("errors and promises", () => {
375377

376378
const app = new App({ test: true });
377379
let error: OwlError;
378-
const mountProm = app
379-
.createRoot(Root)
380+
const root = app.createRoot(Root);
381+
const errorProm = nextAppError(app);
382+
const mountProm = root
380383
.mount(fixture)
381384
.catch((e: Error) => (error = e));
382-
await expect(nextAppError(app)).resolves.toThrow(
385+
await expect(errorProm).resolves.toThrow(
383386
"[Owl] Unhandled error. Destroying the root component"
384387
);
385388
await mountProm;

tests/components/props_validation.test.ts

Lines changed: 63 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { Component, mount, onError, OwlError, props, types as t, xml } from "../../src";
2-
import { App } from "../../src/runtime/app";
3-
import { makeTestFixture, nextAppError, nextTick, render, snapshotEverything } from "../helpers";
1+
import { Component, mount, onError, props, types as t, xml } from "../../src";
2+
import { makeTestFixture, nextTick, render, snapshotEverything } from "../helpers";
43

54
let fixture: HTMLElement;
65

@@ -47,26 +46,22 @@ describe("props validation", () => {
4746
static template = xml`<div><SubComp /></div>`;
4847
}
4948

50-
const app = new App({ test: true });
51-
let error: OwlError | undefined;
52-
const mountProm = app
53-
.createRoot(Parent)
54-
.mount(fixture)
55-
.catch((e: Error) => (error = e));
56-
await expect(nextAppError(app)).resolves.toThrow(
57-
"[Owl] Unhandled error. Destroying the root component"
58-
);
59-
await mountProm;
60-
expect(error!).toBeDefined();
61-
expect(error!.cause.message).toMatch("Invalid component props (SubComp)");
62-
error = undefined;
49+
let error: any;
50+
try {
51+
await mount(Parent, fixture, { test: true });
52+
} catch (e) {
53+
error = e;
54+
}
55+
expect(error).toBeDefined();
56+
expect(error.cause.message).toMatch("Invalid component props (SubComp)");
6357

58+
error = undefined;
6459
try {
6560
await mount(Parent, fixture, { dev: false });
6661
} catch (e) {
67-
error = e as Error;
62+
error = e;
6863
}
69-
expect(error!).toBeUndefined();
64+
expect(error).toBeUndefined();
7065
});
7166

7267
test("props: list of strings", async () => {
@@ -79,18 +74,14 @@ describe("props validation", () => {
7974
static template = xml`<div><SubComp /></div>`;
8075
}
8176

82-
const app = new App({ test: true });
83-
let error: OwlError | undefined;
84-
const mountProm = app
85-
.createRoot(Parent)
86-
.mount(fixture)
87-
.catch((e: Error) => (error = e));
88-
await expect(nextAppError(app)).resolves.toThrow(
89-
"[Owl] Unhandled error. Destroying the root component"
90-
);
91-
await mountProm;
92-
expect(error!).toBeDefined();
93-
expect(error!.cause.message).toMatch("Invalid component props (SubComp)");
77+
let error: any;
78+
try {
79+
await mount(Parent, fixture, { test: true });
80+
} catch (e) {
81+
error = e;
82+
}
83+
expect(error).toBeDefined();
84+
expect(error.cause.message).toMatch("Invalid component props (SubComp)");
9485
});
9586

9687
test("validate props for root component", async () => {
@@ -132,39 +123,35 @@ describe("props validation", () => {
132123
};
133124
(Parent as any).components = { SubComp };
134125

126+
let error: any;
127+
135128
state = {};
136-
let app = new App({ test: true });
137-
let error: OwlError | undefined;
138-
let mountProm = app
139-
.createRoot(Parent)
140-
.mount(fixture)
141-
.catch((e: Error) => (error = e));
142-
await expect(nextAppError(app)).resolves.toThrow(
143-
"[Owl] Unhandled error. Destroying the root component"
144-
);
145-
await mountProm;
146-
expect(error!).toBeDefined();
147-
expect(error!.cause.message).toMatch(`Invalid component props (SubComp)`);
129+
try {
130+
await mount(Parent, fixture, { test: true });
131+
} catch (e) {
132+
error = e;
133+
}
134+
expect(error).toBeDefined();
135+
expect(error.cause.message).toMatch(`Invalid component props (SubComp)`);
136+
148137
error = undefined;
149138
state = { p: test.ok };
150139
try {
151140
await mount(Parent, fixture, { dev: true });
152141
} catch (e) {
153-
error = e as Error;
142+
error = e;
154143
}
155-
expect(error!).toBeUndefined();
144+
expect(error).toBeUndefined();
145+
146+
error = undefined;
156147
state = { p: test.ko };
157-
app = new App({ test: true });
158-
mountProm = app
159-
.createRoot(Parent)
160-
.mount(fixture)
161-
.catch((e: Error) => (error = e));
162-
await expect(nextAppError(app)).resolves.toThrow(
163-
"[Owl] Unhandled error. Destroying the root component"
164-
);
165-
await mountProm;
166-
expect(error!).toBeDefined();
167-
expect(error!.cause.message).toMatch(`Invalid component props (SubComp)`);
148+
try {
149+
await mount(Parent, fixture, { test: true });
150+
} catch (e) {
151+
error = e;
152+
}
153+
expect(error).toBeDefined();
154+
expect(error.cause.message).toMatch(`Invalid component props (SubComp)`);
168155
}
169156
});
170157

@@ -197,17 +184,13 @@ describe("props validation", () => {
197184
}
198185
expect(error!).toBeUndefined();
199186
state = { p: 1 };
200-
const app = new App({ test: true });
201-
const mountProm = app
202-
.createRoot(Parent)
203-
.mount(fixture)
204-
.catch((e: Error) => (error = e));
205-
await expect(nextAppError(app)).resolves.toThrow(
206-
"[Owl] Unhandled error. Destroying the root component"
207-
);
208-
await mountProm;
209-
expect(error!).toBeDefined();
210-
expect(error!.cause.message).toMatch("Invalid component props (SubComp)");
187+
try {
188+
await mount(Parent, fixture, { test: true });
189+
} catch (e) {
190+
error = e;
191+
}
192+
expect(error).toBeDefined();
193+
expect(error.cause.message).toMatch("Invalid component props (SubComp)");
211194
});
212195

213196
test("can validate an optional props", async () => {
@@ -239,17 +222,13 @@ describe("props validation", () => {
239222
}
240223
expect(error!).toBeUndefined();
241224
state = { p: 1 };
242-
const app = new App({ test: true });
243-
const mountProm = app
244-
.createRoot(Parent)
245-
.mount(fixture)
246-
.catch((e: Error) => (error = e));
247-
await expect(nextAppError(app)).resolves.toThrow(
248-
"[Owl] Unhandled error. Destroying the root component"
249-
);
250-
await mountProm;
251-
expect(error!).toBeDefined();
252-
expect(error!.cause.message).toMatch("Invalid component props (SubComp)");
225+
try {
226+
await mount(Parent, fixture, { test: true });
227+
} catch (e) {
228+
error = e;
229+
}
230+
expect(error).toBeDefined();
231+
expect(error.cause.message).toMatch("Invalid component props (SubComp)");
253232
});
254233

255234
test("can validate an array with given primitive type", async () => {
@@ -332,17 +311,13 @@ describe("props validation", () => {
332311
}
333312
expect(error!).toBeUndefined();
334313
state = { p: [true, 1] };
335-
const app = new App({ test: true });
336-
const mountProm = app
337-
.createRoot(Parent)
338-
.mount(fixture)
339-
.catch((e: Error) => (error = e));
340-
await expect(nextAppError(app)).resolves.toThrow(
341-
"[Owl] Unhandled error. Destroying the root component"
342-
);
343-
await mountProm;
344-
expect(error!).toBeDefined();
345-
expect(error!.cause.message).toMatch("Invalid component props (SubComp)");
314+
try {
315+
await mount(Parent, fixture, { test: true });
316+
} catch (e) {
317+
error = e;
318+
}
319+
expect(error).toBeDefined();
320+
expect(error.cause.message).toMatch("Invalid component props (SubComp)");
346321
});
347322

348323
test("can validate an object with simple shape", async () => {

tests/components/reactivity.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,9 @@ describe("reactivity in lifecycle", () => {
185185
const prom = mount(Comp, fixture);
186186
(STATE as any).val = 2;
187187
await prom;
188-
expect(steps).toEqual([2]);
188+
// The first render happens synchronously (fast path, no willStart),
189+
// so the template executes with val=1. Then val=2 triggers a re-render.
190+
expect(steps).toEqual([1, 2]);
189191
expect(fixture.innerHTML).toBe("<div>2</div>");
190192
});
191193

tests/components/refs.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,12 @@ describe("refs", () => {
131131
}
132132

133133
const app = new App({ test: true });
134-
const mountProm = expect(app.createRoot(Test).mount(fixture)).rejects.toThrow(
134+
const root = app.createRoot(Test);
135+
const errorProm = nextAppError(app);
136+
const mountProm = expect(root.mount(fixture)).rejects.toThrow(
135137
'Cannot set the same ref more than once in the same component, ref "coucou" was set multiple times in Test'
136138
);
137-
await expect(nextAppError(app)).resolves.toThrow(
139+
await expect(errorProm).resolves.toThrow(
138140
'Cannot set the same ref more than once in the same component, ref "coucou" was set multiple times in Test'
139141
);
140142
await mountProm;

tests/helpers.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -277,27 +277,21 @@ expect.extend({
277277
});
278278

279279
export function nextAppError(app: any) {
280-
const { _handleError } = app;
281-
const rootPromises = [...app.roots].map((r) => r.promise);
282-
283-
let settled = false;
284-
285-
const done = (error: any, restore = true) => {
286-
if (settled) return;
287-
settled = true;
288-
if (restore) app._handleError = _handleError;
289-
resolve(error);
290-
};
291-
292280
let resolve: (value: any) => void;
293281
const result = new Promise((res) => (resolve = res));
294282

283+
const original = app._handleError;
295284
app._handleError = (error: any) => {
296-
done(error);
285+
app._handleError = original;
286+
resolve(error);
297287
};
298288

299-
for (const p of rootPromises) {
300-
p.catch((err: any) => done(err));
289+
// Also catch rejections from root mount promises
290+
for (const root of app.roots) {
291+
root.promise.catch((err: any) => {
292+
app._handleError = original;
293+
resolve(err);
294+
});
301295
}
302296

303297
return result;

0 commit comments

Comments
 (0)